Merge reference-apps 2ce3e68a into master (gate: green on 2ce3e68a, recorded by tools/ci/pre-push.sh; landed on the box mirror)
This commit is contained in:
commit
194871c014
18 changed files with 670 additions and 153 deletions
65
docs/build/compatibility.md
vendored
65
docs/build/compatibility.md
vendored
|
|
@ -2,74 +2,81 @@
|
|||
|
||||
A GPU-secured network for Ethereum-compatible applications and verifiable computation. Compatibility is a product deliverable here, not a sentence: every row below is a test that ran against the devnet from the repository (`tools/reference-apps/compat/run.mjs`), with its evidence, and a row that could not run says so and why. Devnet, no value.
|
||||
|
||||
Last run: 2026-10-08 16:34 UTC on igneum-devnet-4 (chain id 4465). 20 passed, 0 failed, 0 untested. Sender 0x27C433aE…, contracts compiled with solc 0.8.28+commit.7893614a.Emscripten.clang.
|
||||
Last run: 2026-10-08 17:45 UTC on igneum-devnet-4 (chain id 4465), run id ra-20261008T1744-evm. 22 passed, 0 failed, 0 untested. Sender 0x27C433aE…, contracts compiled with solc 0.8.28+commit.7893614a.Emscripten.clang.
|
||||
|
||||
Three boundaries hold for everything on this page: proven execution is not finality; EVM compatibility is not Ethereum security; ZK is not privacy. The four words included, executed, proven and finalised are defined on [/receipt](/receipt).
|
||||
|
||||
## Representative contracts
|
||||
## Representative contracts (register case EVM-05)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| an ERC-20 deploys (CREATE address as Ethereum computes it, code at the address) | **passed** | at 0xb2495fd85f54d73d7a13059a8e61956b67ad7aed, block 739, gas 436751 of 639078 estimated | tx 0x8fd280cb…a5b9, address 0xb2495fd85f54d73d7a13059a8e61956b67ad7aed, block 739, gasUsed 436751, estimated 639078 |
|
||||
| ERC-20 transfer: balances move, the Transfer log decodes, eth_call reads the new balance | **passed** | 5 CTT to 0x00000000, log decoded, balance read | tx 0xe787770a…25fb, block 742 |
|
||||
| ERC-20 approve then transferFrom by a spender (the sender approves itself as spender): allowance consumed, logs decode | **passed** | allowance 3 then 1 after transferFrom of 2 | approve 0xfc7a3a64…5d24, transferFrom 0x6c0a22dc…ef22 |
|
||||
| a probe contract deploys (counter, block context reader, revert, out-of-gas loop, CREATE2) | **passed** | at 0x3efc60794d574627eb5cb4eca77d96735dac4c42 | tx 0x551c2403…8e15, address 0x3efc60794d574627eb5cb4eca77d96735dac4c42, block 751 |
|
||||
| a counter increments twice and reads 2 (state persists across chain blocks) | **passed** | count 2 after blocks 753 and 755 | first 0xfaf85576…75a1, second 0x97ca4151…19c8 |
|
||||
| an ERC-20 deploys (CREATE address as Ethereum computes it, code at the address) | **passed** | at 0xd93f5e2091ccd544757a38ecf5e4fa176c141b62, block 3016, gas 436751 of 639078 estimated | tx 0x314c059b…a679, address 0xd93f5e2091ccd544757a38ecf5e4fa176c141b62, block 3016, gasUsed 436751, estimated 639078 |
|
||||
| ERC-20 transfer: balances move, the Transfer log decodes, eth_call reads the new balance | **passed** | 5 CTT to 0x00000000, log decoded, balance read | tx 0xd8dbd034…15ce, block 3018 |
|
||||
| ERC-20 approve then transferFrom by a spender (the sender approves itself as spender): allowance consumed, logs decode | **passed** | allowance 3 then 1 after transferFrom of 2 | approve 0x570bbe86…52c8, transferFrom 0xe6031b34…cd5f |
|
||||
| a probe contract deploys (counter, block context reader, revert, out-of-gas loop, CREATE2) | **passed** | at 0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d | tx 0x97e39c81…bdfa, address 0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d, block 3026 |
|
||||
| a counter increments twice and reads 2 (state persists across chain blocks) | **passed** | count 2 after blocks 3029 and 3032 | first 0x1771f03c…cd3a, second 0xad3f6594…c013 |
|
||||
|
||||
## Wallet fee estimation
|
||||
## Transaction binding and replay (register case EVM-02)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| a transaction signed for another chain id (the previous devnet's 4464) is refused by the node | **passed** | refused: wrong chain id: expected 4465, got Some(4464) | chain_id_signed 4464 |
|
||||
| the same signed transaction sent twice: the second send is refused (nonce already used) | **passed** | first send mined in block 3049; the replay refused: nonce too low: account nonce is 40, transaction has 39 | tx 0xb1722ee0…a36f, block 3049, nonce_after 40 |
|
||||
|
||||
## Wallet fee estimation (register case EVM-03)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| eth_gasPrice, eth_maxPriorityFeePerGas and eth_feeHistory answer in Ethereum's shapes | **passed** | gasPrice 196.130804856 gwei, priority 1 gwei, feeHistory 5 base fees | gasPrice 0x2daa4e9478, maxPriorityFeePerGas 0x3b9aca00 |
|
||||
| a plain transfer: eth_estimateGas against the receipt's gasUsed (the quote folds proving gas; both above Ethereum's 21,000) | **passed** | estimated 59650, used 21000 (35% of the estimate); Ethereum's figure is 21,000 | tx 0x99c4dc1a…cad0, estimated 59650, gasUsed 21000, ethereum 21000 |
|
||||
| a contract call: eth_estimateGas against gasUsed (a wallet that quotes from the node is covered) | **passed** | estimated 134366, used 26371 | tx 0xeb8bafa3…bee4, estimated 134366, gasUsed 26371 |
|
||||
| the documented difference: a transfer sent with a local 21,000 gas limit (Ethereum's intrinsic cost) instead of the node's quote | **passed** | the node accepted it and it succeeded with gasUsed 21000: a wallet with a hard-coded 21,000 works | sent true, hash 0x538fdf72…b121, status success, gasUsed 21000 |
|
||||
| a plain transfer: eth_estimateGas against the receipt's gasUsed (the quote folds proving gas; both above Ethereum's 21,000) | **passed** | estimated 59650, used 21000 (35% of the estimate); Ethereum's figure is 21,000 | tx 0x6e67cf09…c795, estimated 59650, gasUsed 21000, ethereum 21000 |
|
||||
| a contract call: eth_estimateGas against gasUsed (a wallet that quotes from the node is covered) | **passed** | estimated 134366, used 26371 | tx 0xc14d3a76…31c6, estimated 134366, gasUsed 26371 |
|
||||
| the documented difference: a transfer sent with a local 21,000 gas limit (Ethereum's intrinsic cost) instead of the node's quote | **passed** | the node accepted it and it succeeded with gasUsed 21000: a wallet with a hard-coded 21,000 works | sent true, hash 0x1581f608…2c32, status success, gasUsed 21000 |
|
||||
|
||||
## Indexing
|
||||
## Indexing (register case EVM-06)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| eth_getLogs by address, by topic and by block range returns the receipts' logs with consistent logIndex and transactionIndex | **passed** | 4 logs by address, 3 Transfer logs by topic, each found in its receipt | byAddress 4, byTopic 3, fromBlock 739 |
|
||||
| eth_getBlockByNumber with full transactions, eth_getBlockReceipts and eth_getTransactionByHash agree on indices and hashes | **passed** | block 742: 1 transactions, indices 0 to 0 continuous | block 742, count 1 |
|
||||
| eth_getLogs by address, by topic and by block range returns the receipts' logs with consistent logIndex and transactionIndex | **passed** | 4 logs by address, 3 Transfer logs by topic, each found in its receipt | byAddress 4, byTopic 3, fromBlock 3016 |
|
||||
| eth_getBlockByNumber with full transactions, eth_getBlockReceipts and eth_getTransactionByHash agree on indices and hashes | **passed** | block 3018: 1 transactions, indices 0 to 0 continuous | block 3018, count 1 |
|
||||
|
||||
## Failed transactions
|
||||
## Failed transactions (register case EVM-05)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| a revert with a reason: eth_call returns the reason, the sent transaction has status 0, gas is charged, the nonce advances | **passed** | eth_call answers code 3 "execution reverted" with Error(string) data decoding to "compat says no"; the sent transaction: status 0, gasUsed 22292 of 200000, nonce 23 to 24 | tx 0xfcdf8d24…6a1e, gasUsed 22292, block 762, call_error_code 3, call_error_message execution reverted, revert_data 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e636f6d7061742073617973206e6f000000000000000000000000000000000000 |
|
||||
| an out-of-gas call: status 0 and the whole limit charged | **passed** | status 0, gasUsed 150000 of the 150000 limit | tx 0xb3986b45…a1f6, gasUsed 150000, limit 150000 |
|
||||
| a revert with a reason: eth_call returns the reason, the sent transaction has status 0, gas is charged, the nonce advances | **passed** | eth_call answers code 3 "execution reverted" with Error(string) data decoding to "compat says no"; the sent transaction: status 0, gasUsed 22292 of 200000, nonce 37 to 38 | tx 0x90e50327…1344, gasUsed 22292, block 3044, call_error_code 3, call_error_message execution reverted, revert_data 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e636f6d7061742073617973206e6f000000000000000000000000000000000000 |
|
||||
| an out-of-gas call: status 0 and the whole limit charged | **passed** | status 0, gasUsed 150000 of the 150000 limit | tx 0x4d138920…e5f0, gasUsed 150000, limit 150000 |
|
||||
|
||||
## Receipts
|
||||
## Receipts (register case EVM-06)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| a receipt carries every Ethereum field, logsBloom recomputes from its logs, contractAddress is set on creation | **passed** | 15 fields, bloom recomputed over 1 log(s), contractAddress 0xb2495fd8 | tx 0x8fd280cb…a5b9 |
|
||||
| the block's receipts rebuild its receipts trie root (Ethereum's layout, the node's encoding) equal to the header's receiptsRoot | **passed** | block 742, 1 receipts, root 0xa28061003c4c | block 742, receipts 1, receiptsRoot 0xa2806100…ac55 |
|
||||
| a receipt carries every Ethereum field, logsBloom recomputes from its logs, contractAddress is set on creation | **passed** | 15 fields, bloom recomputed over 1 log(s), contractAddress 0xd93f5e20 | tx 0x314c059b…a679 |
|
||||
| the block's receipts rebuild its receipts trie root (Ethereum's layout, the node's encoding) equal to the header's receiptsRoot | **passed** | block 3018, 1 receipts, root 0xc443c3e57c60 | block 3018, receipts 1, receiptsRoot 0xc443c3e5…8836 |
|
||||
|
||||
## Application assumptions
|
||||
## Application assumptions (register case EVM-04)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| block.number, timestamp, chainid, basefee and blockhash(number-1) read from a contract match the block the call executed in | **passed** | block 768: number, timestamp, chainid 4465, basefee, blockhash(n-1) = parentHash, msg.sender and tx.origin all match | tx 0x3c1d2dff…4fcd, block 768 |
|
||||
| CREATE2 lands at the address Ethereum computes (keccak(0xff, deployer, salt, keccak(initcode))) | **passed** | child at 0xE63036B0fEC0d01321Ef4DA62C084d1Ec2eA47be | tx 0x11cd9873…a80c, address 0xE63036B0fEC0d01321Ef4DA62C084d1Ec2eA47be |
|
||||
| block.number, timestamp, chainid, basefee and blockhash(number-1) read from a contract match the block the call executed in | **passed** | block 3053: number, timestamp, chainid 4465, basefee, blockhash(n-1) = parentHash, msg.sender and tx.origin all match | tx 0x186def3b…a228, block 3053 |
|
||||
| CREATE2 lands at the address Ethereum computes (keccak(0xff, deployer, salt, keccak(initcode))) | **passed** | child at 0xe3bC69915FB7318236c5ff31EF679aF6ae5b8699 | tx 0x442569b2…af11, address 0xe3bC69915FB7318236c5ff31EF679aF6ae5b8699 |
|
||||
|
||||
## Differences: block context
|
||||
## Differences: block context (register case EVM-04)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| block.coinbase is the miner of the DAG block that first included the transaction, not one miner per chain block (measured: coinbase against the block's igneum.mergeset) | **passed** | coinbase 0xc119093F = the including block's miner 0xc119093f; the chain block merged 2 block(s) with 1 distinct miner(s), chain block miner 0xc119093f | tx 0x3c1d2dff…4fcd, coinbase 0xc119093F38E3764Fdd86ea9635b7f616DEC6C3A6, includingMiner 0xc119093f38e3764fdd86ea9635b7f616dec6c3a6, chainBlockMiner 0xc119093f38e3764fdd86ea9635b7f616dec6c3a6 |
|
||||
| block.coinbase is the miner of the DAG block that first included the transaction, not one miner per chain block (measured: coinbase against the block's igneum.mergeset) | **passed** | coinbase 0x1450a9d8 = the including block's miner 0x1450a9d8; the chain block merged 1 block(s) with 1 distinct miner(s), chain block miner 0x1450a9d8 | tx 0x186def3b…a228, coinbase 0x1450a9d804D29084d34b93afB001dE807Aa9920d, includingMiner 0x1450a9d804d29084d34b93afb001de807aa9920d, chainBlockMiner 0x1450a9d804d29084d34b93afb001de807aa9920d |
|
||||
|
||||
## Differences: randomness
|
||||
## Differences: randomness (register case EVM-04)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| block.prevrandao: its value is derived from the chain's epoch seed (not Ethereum's beacon RANDAO); measured across consecutive blocks, and from a contract when a sender is funded | **passed** | blocks 767 to 770: 1 distinct epoch seed(s), 4 distinct mixHash value(s) in the headers; the source is the chain's epoch seed, which holds for an epoch, so prevrandao is not fresh per block and must not seed a lottery; a contract reading it twice in one call got the same value, and at block 768 it read 0x9cf17fa10c95… | |
|
||||
| block.prevrandao: its value is derived from the chain's epoch seed (not Ethereum's beacon RANDAO); measured across consecutive blocks, and from a contract when a sender is funded | **passed** | blocks 3051 to 3054: 1 distinct epoch seed(s), 4 distinct mixHash value(s) in the headers; the source is the chain's epoch seed, which holds for an epoch, so prevrandao is not fresh per block and must not seed a lottery; a contract reading it twice in one call got the same value, and at block 3053 it read 0x3b9abd95a495… | |
|
||||
|
||||
## Differences: two-dimensional fees
|
||||
## Differences: two-dimensional fees (register case EVM-03)
|
||||
|
||||
| Row | Verdict | What was measured | Evidence |
|
||||
|---|---|---|---|
|
||||
| a receipt carries pgasUsed, provingBaseFeePerGas, burnedProvingFee and burnedExecutionBaseFee; effectiveGasPrice covers both dimensions | **passed** | gasUsed 51375, pgasUsed 1968, proving base fee 10000 gwei per pgas; paid 5188875000000000 wei, of which proving burn 19680000000000000, execution burn 5137500000000000, tip 41100000000000 (parts sum differs from gasUsed × effectiveGasPrice) | tx 0xe787770a…25fb, gasUsed 51375, pgasUsed 1968, effectiveGasPrice 0x178411b200, parts_equal_paid false |
|
||||
| a receipt carries pgasUsed, provingBaseFeePerGas, burnedProvingFee and burnedExecutionBaseFee; effectiveGasPrice covers both dimensions | **passed** | gasUsed 51375, pgasUsed 1968, proving base fee 10000 gwei per pgas; paid 5188875000000000 wei, of which proving burn 19680000000000000, execution burn 5137500000000000, tip 41100000000000 (parts sum differs from gasUsed × effectiveGasPrice) | tx 0xd8dbd034…15ce, gasUsed 51375, pgasUsed 1968, effectiveGasPrice 0x178411b200, parts_equal_paid false |
|
||||
|
||||
## What the differences mean for an application
|
||||
|
||||
|
|
|
|||
|
|
@ -248,38 +248,41 @@ table{min-width:560px}
|
|||
<section class="section compact"><div class="container">
|
||||
|
||||
<div class="docs-layout">
|
||||
<nav class="doc-nav" aria-label="Contents"><div class="eyebrow">Contents</div><a href="#representative-contracts">Representative contracts</a><a href="#wallet-fee-estimation">Wallet fee estimation</a><a href="#indexing">Indexing</a><a href="#failed-transactions">Failed transactions</a><a href="#receipts">Receipts</a><a href="#application-assumptions">Application assumptions</a><a href="#differences-block-context">Differences: block context</a><a href="#differences-randomness">Differences: randomness</a><a href="#differences-two-dimensional-fees">Differences: two-dimensional fees</a><a href="#what-the-differences-mean-for-an-application">What the differences mean for an application</a></nav>
|
||||
<nav class="doc-nav" aria-label="Contents"><div class="eyebrow">Contents</div><a href="#representative-contracts-register-case-evm-05">Representative contracts (register case EVM-05)</a><a href="#transaction-binding-and-replay-register-case-evm-02">Transaction binding and replay (register case EVM-02)</a><a href="#wallet-fee-estimation-register-case-evm-03">Wallet fee estimation (register case EVM-03)</a><a href="#indexing-register-case-evm-06">Indexing (register case EVM-06)</a><a href="#failed-transactions-register-case-evm-05">Failed transactions (register case EVM-05)</a><a href="#receipts-register-case-evm-06">Receipts (register case EVM-06)</a><a href="#application-assumptions-register-case-evm-04">Application assumptions (register case EVM-04)</a><a href="#differences-block-context-register-case-evm-04">Differences: block context (register case EVM-04)</a><a href="#differences-randomness-register-case-evm-04">Differences: randomness (register case EVM-04)</a><a href="#differences-two-dimensional-fees-register-case-evm-03">Differences: two-dimensional fees (register case EVM-03)</a><a href="#what-the-differences-mean-for-an-application">What the differences mean for an application</a></nav>
|
||||
<article class="doc-body" data-filter-list="compatibility"><section class="doc-section"><h1 id="compatibility-measured">Compatibility, measured</h1>
|
||||
<p>A GPU-secured network for Ethereum-compatible applications and verifiable computation. Compatibility is a product deliverable here, not a sentence: every row below is a test that ran against the devnet from the repository (<code>tools/reference-apps/compat/run.mjs</code>), with its evidence, and a row that could not run says so and why. Devnet, no value.</p>
|
||||
<p>Last run: 2026-10-08 16:34 UTC on igneum-devnet-4 (chain id 4465). 20 passed, 0 failed, 0 untested. Sender 0x27C433aE…, contracts compiled with solc 0.8.28+commit.7893614a.Emscripten.clang.</p>
|
||||
<p>Last run: 2026-10-08 17:45 UTC on igneum-devnet-4 (chain id 4465), run id ra-20261008T1744-evm. 22 passed, 0 failed, 0 untested. Sender 0x27C433aE…, contracts compiled with solc 0.8.28+commit.7893614a.Emscripten.clang.</p>
|
||||
<p>Three boundaries hold for everything on this page: proven execution is not finality; EVM compatibility is not Ethereum security; ZK is not privacy. The four words included, executed, proven and finalised are defined on <a href="/receipt">/receipt</a>.</p>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="representative-contracts">Representative contracts</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>an ERC-20 deploys (CREATE address as Ethereum computes it, code at the address)</td><td><strong>passed</strong></td><td>at 0xb2495fd85f54d73d7a13059a8e61956b67ad7aed, block 739, gas 436751 of 639078 estimated</td><td>tx 0x8fd280cb…a5b9, address 0xb2495fd85f54d73d7a13059a8e61956b67ad7aed, block 739, gasUsed 436751, estimated 639078</td></tr><tr><td>ERC-20 transfer: balances move, the Transfer log decodes, eth_call reads the new balance</td><td><strong>passed</strong></td><td>5 CTT to 0x00000000, log decoded, balance read</td><td>tx 0xe787770a…25fb, block 742</td></tr><tr><td>ERC-20 approve then transferFrom by a spender (the sender approves itself as spender): allowance consumed, logs decode</td><td><strong>passed</strong></td><td>allowance 3 then 1 after transferFrom of 2</td><td>approve 0xfc7a3a64…5d24, transferFrom 0x6c0a22dc…ef22</td></tr><tr><td>a probe contract deploys (counter, block context reader, revert, out-of-gas loop, CREATE2)</td><td><strong>passed</strong></td><td>at 0x3efc60794d574627eb5cb4eca77d96735dac4c42</td><td>tx 0x551c2403…8e15, address 0x3efc60794d574627eb5cb4eca77d96735dac4c42, block 751</td></tr><tr><td>a counter increments twice and reads 2 (state persists across chain blocks)</td><td><strong>passed</strong></td><td>count 2 after blocks 753 and 755</td><td>first 0xfaf85576…75a1, second 0x97ca4151…19c8</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="representative-contracts-register-case-evm-05">Representative contracts (register case EVM-05)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>an ERC-20 deploys (CREATE address as Ethereum computes it, code at the address)</td><td><strong>passed</strong></td><td>at 0xd93f5e2091ccd544757a38ecf5e4fa176c141b62, block 3016, gas 436751 of 639078 estimated</td><td>tx 0x314c059b…a679, address 0xd93f5e2091ccd544757a38ecf5e4fa176c141b62, block 3016, gasUsed 436751, estimated 639078</td></tr><tr><td>ERC-20 transfer: balances move, the Transfer log decodes, eth_call reads the new balance</td><td><strong>passed</strong></td><td>5 CTT to 0x00000000, log decoded, balance read</td><td>tx 0xd8dbd034…15ce, block 3018</td></tr><tr><td>ERC-20 approve then transferFrom by a spender (the sender approves itself as spender): allowance consumed, logs decode</td><td><strong>passed</strong></td><td>allowance 3 then 1 after transferFrom of 2</td><td>approve 0x570bbe86…52c8, transferFrom 0xe6031b34…cd5f</td></tr><tr><td>a probe contract deploys (counter, block context reader, revert, out-of-gas loop, CREATE2)</td><td><strong>passed</strong></td><td>at 0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d</td><td>tx 0x97e39c81…bdfa, address 0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d, block 3026</td></tr><tr><td>a counter increments twice and reads 2 (state persists across chain blocks)</td><td><strong>passed</strong></td><td>count 2 after blocks 3029 and 3032</td><td>first 0x1771f03c…cd3a, second 0xad3f6594…c013</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="wallet-fee-estimation">Wallet fee estimation</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>eth_gasPrice, eth_maxPriorityFeePerGas and eth_feeHistory answer in Ethereum's shapes</td><td><strong>passed</strong></td><td>gasPrice 196.130804856 gwei, priority 1 gwei, feeHistory 5 base fees</td><td>gasPrice 0x2daa4e9478, maxPriorityFeePerGas 0x3b9aca00</td></tr><tr><td>a plain transfer: eth_estimateGas against the receipt's gasUsed (the quote folds proving gas; both above Ethereum's 21,000)</td><td><strong>passed</strong></td><td>estimated 59650, used 21000 (35% of the estimate); Ethereum's figure is 21,000</td><td>tx 0x99c4dc1a…cad0, estimated 59650, gasUsed 21000, ethereum 21000</td></tr><tr><td>a contract call: eth_estimateGas against gasUsed (a wallet that quotes from the node is covered)</td><td><strong>passed</strong></td><td>estimated 134366, used 26371</td><td>tx 0xeb8bafa3…bee4, estimated 134366, gasUsed 26371</td></tr><tr><td>the documented difference: a transfer sent with a local 21,000 gas limit (Ethereum's intrinsic cost) instead of the node's quote</td><td><strong>passed</strong></td><td>the node accepted it and it succeeded with gasUsed 21000: a wallet with a hard-coded 21,000 works</td><td>sent true, hash 0x538fdf72…b121, status success, gasUsed 21000</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="transaction-binding-and-replay-register-case-evm-02">Transaction binding and replay (register case EVM-02)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a transaction signed for another chain id (the previous devnet's 4464) is refused by the node</td><td><strong>passed</strong></td><td>refused: wrong chain id: expected 4465, got Some(4464)</td><td>chain_id_signed 4464</td></tr><tr><td>the same signed transaction sent twice: the second send is refused (nonce already used)</td><td><strong>passed</strong></td><td>first send mined in block 3049; the replay refused: nonce too low: account nonce is 40, transaction has 39</td><td>tx 0xb1722ee0…a36f, block 3049, nonce_after 40</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="indexing">Indexing</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>eth_getLogs by address, by topic and by block range returns the receipts' logs with consistent logIndex and transactionIndex</td><td><strong>passed</strong></td><td>4 logs by address, 3 Transfer logs by topic, each found in its receipt</td><td>byAddress 4, byTopic 3, fromBlock 739</td></tr><tr><td>eth_getBlockByNumber with full transactions, eth_getBlockReceipts and eth_getTransactionByHash agree on indices and hashes</td><td><strong>passed</strong></td><td>block 742: 1 transactions, indices 0 to 0 continuous</td><td>block 742, count 1</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="wallet-fee-estimation-register-case-evm-03">Wallet fee estimation (register case EVM-03)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>eth_gasPrice, eth_maxPriorityFeePerGas and eth_feeHistory answer in Ethereum's shapes</td><td><strong>passed</strong></td><td>gasPrice 196.130804856 gwei, priority 1 gwei, feeHistory 5 base fees</td><td>gasPrice 0x2daa4e9478, maxPriorityFeePerGas 0x3b9aca00</td></tr><tr><td>a plain transfer: eth_estimateGas against the receipt's gasUsed (the quote folds proving gas; both above Ethereum's 21,000)</td><td><strong>passed</strong></td><td>estimated 59650, used 21000 (35% of the estimate); Ethereum's figure is 21,000</td><td>tx 0x6e67cf09…c795, estimated 59650, gasUsed 21000, ethereum 21000</td></tr><tr><td>a contract call: eth_estimateGas against gasUsed (a wallet that quotes from the node is covered)</td><td><strong>passed</strong></td><td>estimated 134366, used 26371</td><td>tx 0xc14d3a76…31c6, estimated 134366, gasUsed 26371</td></tr><tr><td>the documented difference: a transfer sent with a local 21,000 gas limit (Ethereum's intrinsic cost) instead of the node's quote</td><td><strong>passed</strong></td><td>the node accepted it and it succeeded with gasUsed 21000: a wallet with a hard-coded 21,000 works</td><td>sent true, hash 0x1581f608…2c32, status success, gasUsed 21000</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="failed-transactions">Failed transactions</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a revert with a reason: eth_call returns the reason, the sent transaction has status 0, gas is charged, the nonce advances</td><td><strong>passed</strong></td><td>eth_call answers code 3 "execution reverted" with Error(string) data decoding to "compat says no"; the sent transaction: status 0, gasUsed 22292 of 200000, nonce 23 to 24</td><td>tx 0xfcdf8d24…6a1e, gasUsed 22292, block 762, call_error_code 3, call_error_message execution reverted, revert_data 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e636f6d7061742073617973206e6f000000000000000000000000000000000000</td></tr><tr><td>an out-of-gas call: status 0 and the whole limit charged</td><td><strong>passed</strong></td><td>status 0, gasUsed 150000 of the 150000 limit</td><td>tx 0xb3986b45…a1f6, gasUsed 150000, limit 150000</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="indexing-register-case-evm-06">Indexing (register case EVM-06)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>eth_getLogs by address, by topic and by block range returns the receipts' logs with consistent logIndex and transactionIndex</td><td><strong>passed</strong></td><td>4 logs by address, 3 Transfer logs by topic, each found in its receipt</td><td>byAddress 4, byTopic 3, fromBlock 3016</td></tr><tr><td>eth_getBlockByNumber with full transactions, eth_getBlockReceipts and eth_getTransactionByHash agree on indices and hashes</td><td><strong>passed</strong></td><td>block 3018: 1 transactions, indices 0 to 0 continuous</td><td>block 3018, count 1</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="receipts">Receipts</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a receipt carries every Ethereum field, logsBloom recomputes from its logs, contractAddress is set on creation</td><td><strong>passed</strong></td><td>15 fields, bloom recomputed over 1 log(s), contractAddress 0xb2495fd8</td><td>tx 0x8fd280cb…a5b9</td></tr><tr><td>the block's receipts rebuild its receipts trie root (Ethereum's layout, the node's encoding) equal to the header's receiptsRoot</td><td><strong>passed</strong></td><td>block 742, 1 receipts, root 0xa28061003c4c</td><td>block 742, receipts 1, receiptsRoot 0xa2806100…ac55</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="failed-transactions-register-case-evm-05">Failed transactions (register case EVM-05)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a revert with a reason: eth_call returns the reason, the sent transaction has status 0, gas is charged, the nonce advances</td><td><strong>passed</strong></td><td>eth_call answers code 3 "execution reverted" with Error(string) data decoding to "compat says no"; the sent transaction: status 0, gasUsed 22292 of 200000, nonce 37 to 38</td><td>tx 0x90e50327…1344, gasUsed 22292, block 3044, call_error_code 3, call_error_message execution reverted, revert_data 0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e636f6d7061742073617973206e6f000000000000000000000000000000000000</td></tr><tr><td>an out-of-gas call: status 0 and the whole limit charged</td><td><strong>passed</strong></td><td>status 0, gasUsed 150000 of the 150000 limit</td><td>tx 0x4d138920…e5f0, gasUsed 150000, limit 150000</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="application-assumptions">Application assumptions</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>block.number, timestamp, chainid, basefee and blockhash(number-1) read from a contract match the block the call executed in</td><td><strong>passed</strong></td><td>block 768: number, timestamp, chainid 4465, basefee, blockhash(n-1) = parentHash, msg.sender and tx.origin all match</td><td>tx 0x3c1d2dff…4fcd, block 768</td></tr><tr><td>CREATE2 lands at the address Ethereum computes (keccak(0xff, deployer, salt, keccak(initcode)))</td><td><strong>passed</strong></td><td>child at 0xE63036B0fEC0d01321Ef4DA62C084d1Ec2eA47be</td><td>tx 0x11cd9873…a80c, address 0xE63036B0fEC0d01321Ef4DA62C084d1Ec2eA47be</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="receipts-register-case-evm-06">Receipts (register case EVM-06)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a receipt carries every Ethereum field, logsBloom recomputes from its logs, contractAddress is set on creation</td><td><strong>passed</strong></td><td>15 fields, bloom recomputed over 1 log(s), contractAddress 0xd93f5e20</td><td>tx 0x314c059b…a679</td></tr><tr><td>the block's receipts rebuild its receipts trie root (Ethereum's layout, the node's encoding) equal to the header's receiptsRoot</td><td><strong>passed</strong></td><td>block 3018, 1 receipts, root 0xc443c3e57c60</td><td>block 3018, receipts 1, receiptsRoot 0xc443c3e5…8836</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="differences-block-context">Differences: block context</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>block.coinbase is the miner of the DAG block that first included the transaction, not one miner per chain block (measured: coinbase against the block's igneum.mergeset)</td><td><strong>passed</strong></td><td>coinbase 0xc119093F = the including block's miner 0xc119093f; the chain block merged 2 block(s) with 1 distinct miner(s), chain block miner 0xc119093f</td><td>tx 0x3c1d2dff…4fcd, coinbase 0xc119093F38E3764Fdd86ea9635b7f616DEC6C3A6, includingMiner 0xc119093f38e3764fdd86ea9635b7f616dec6c3a6, chainBlockMiner 0xc119093f38e3764fdd86ea9635b7f616dec6c3a6</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="application-assumptions-register-case-evm-04">Application assumptions (register case EVM-04)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>block.number, timestamp, chainid, basefee and blockhash(number-1) read from a contract match the block the call executed in</td><td><strong>passed</strong></td><td>block 3053: number, timestamp, chainid 4465, basefee, blockhash(n-1) = parentHash, msg.sender and tx.origin all match</td><td>tx 0x186def3b…a228, block 3053</td></tr><tr><td>CREATE2 lands at the address Ethereum computes (keccak(0xff, deployer, salt, keccak(initcode)))</td><td><strong>passed</strong></td><td>child at 0xe3bC69915FB7318236c5ff31EF679aF6ae5b8699</td><td>tx 0x442569b2…af11, address 0xe3bC69915FB7318236c5ff31EF679aF6ae5b8699</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="differences-randomness">Differences: randomness</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>block.prevrandao: its value is derived from the chain's epoch seed (not Ethereum's beacon RANDAO); measured across consecutive blocks, and from a contract when a sender is funded</td><td><strong>passed</strong></td><td>blocks 767 to 770: 1 distinct epoch seed(s), 4 distinct mixHash value(s) in the headers; the source is the chain's epoch seed, which holds for an epoch, so prevrandao is not fresh per block and must not seed a lottery; a contract reading it twice in one call got the same value, and at block 768 it read 0x9cf17fa10c95…</td><td></td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="differences-block-context-register-case-evm-04">Differences: block context (register case EVM-04)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>block.coinbase is the miner of the DAG block that first included the transaction, not one miner per chain block (measured: coinbase against the block's igneum.mergeset)</td><td><strong>passed</strong></td><td>coinbase 0x1450a9d8 = the including block's miner 0x1450a9d8; the chain block merged 1 block(s) with 1 distinct miner(s), chain block miner 0x1450a9d8</td><td>tx 0x186def3b…a228, coinbase 0x1450a9d804D29084d34b93afB001dE807Aa9920d, includingMiner 0x1450a9d804d29084d34b93afb001de807aa9920d, chainBlockMiner 0x1450a9d804d29084d34b93afb001de807aa9920d</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="differences-two-dimensional-fees">Differences: two-dimensional fees</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a receipt carries pgasUsed, provingBaseFeePerGas, burnedProvingFee and burnedExecutionBaseFee; effectiveGasPrice covers both dimensions</td><td><strong>passed</strong></td><td>gasUsed 51375, pgasUsed 1968, proving base fee 10000 gwei per pgas; paid 5188875000000000 wei, of which proving burn 19680000000000000, execution burn 5137500000000000, tip 41100000000000 (parts sum differs from gasUsed × effectiveGasPrice)</td><td>tx 0xe787770a…25fb, gasUsed 51375, pgasUsed 1968, effectiveGasPrice 0x178411b200, parts_equal_paid false</td></tr></tbody></table></div>
|
||||
<section class="doc-section"><h2 id="differences-randomness-register-case-evm-04">Differences: randomness (register case EVM-04)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>block.prevrandao: its value is derived from the chain's epoch seed (not Ethereum's beacon RANDAO); measured across consecutive blocks, and from a contract when a sender is funded</td><td><strong>passed</strong></td><td>blocks 3051 to 3054: 1 distinct epoch seed(s), 4 distinct mixHash value(s) in the headers; the source is the chain's epoch seed, which holds for an epoch, so prevrandao is not fresh per block and must not seed a lottery; a contract reading it twice in one call got the same value, and at block 3053 it read 0x3b9abd95a495…</td><td></td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="differences-two-dimensional-fees-register-case-evm-03">Differences: two-dimensional fees (register case EVM-03)</h2>
|
||||
<div class="tbl"><table><thead><tr><th>Row</th><th>Verdict</th><th>What was measured</th><th>Evidence</th></tr></thead><tbody><tr><td>a receipt carries pgasUsed, provingBaseFeePerGas, burnedProvingFee and burnedExecutionBaseFee; effectiveGasPrice covers both dimensions</td><td><strong>passed</strong></td><td>gasUsed 51375, pgasUsed 1968, proving base fee 10000 gwei per pgas; paid 5188875000000000 wei, of which proving burn 19680000000000000, execution burn 5137500000000000, tip 41100000000000 (parts sum differs from gasUsed × effectiveGasPrice)</td><td>tx 0xd8dbd034…15ce, gasUsed 51375, pgasUsed 1968, effectiveGasPrice 0x178411b200, parts_equal_paid false</td></tr></tbody></table></div>
|
||||
</section>
|
||||
<section class="doc-section"><h2 id="what-the-differences-mean-for-an-application">What the differences mean for an application</h2>
|
||||
<ul><li>Block context: <code>block.coinbase</code> is the miner of the DAG block that first included the transaction, so one chain block can carry transactions with different coinbases; code that pays or trusts <code>block.coinbase</code> as "the block producer" sees several producers per block.</li><li>Randomness: <code>block.prevrandao</code> derives from the chain's epoch seed and holds for an epoch, so it is not fresh per block and must not seed a lottery or a commitment; use an application-level randomness source.</li><li>Two-dimensional fees: every transaction pays execution gas and proving gas; the node folds the second into the gas it quotes, so a wallet that quotes from the node (eth_estimateGas, eth_gasPrice, eth_feeHistory) is covered and a wallet with Ethereum's constants (21,000 for a transfer) is not; the receipt carries the split under <code>igneum</code> (pgasUsed, provingBaseFeePerGas, burnedProvingFee, burnedExecutionBaseFee, minerTip).</li></ul>
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const res = existsSync(src) ? JSON.parse(readFileSync(src, 'utf8')) : { meta: {}
|
|||
const esc = s => String(s).replace(/\|/g, '\\|');
|
||||
const short = h => (typeof h === 'string' && /^0x[0-9a-f]{64}$/i.test(h) ? h.slice(0, 10) + '…' + h.slice(-4) : h);
|
||||
const evidence = r => Object.entries(r.evidence || {}).filter(([, v]) => ['string', 'number', 'boolean'].includes(typeof v)).map(([k, v]) => `${k} ${short(v)}`).join(', ');
|
||||
const ITEMS = ['Representative contracts', 'Wallet fee estimation', 'Indexing', 'Failed transactions', 'Receipts', 'Application assumptions', 'Differences: block context', 'Differences: randomness', 'Differences: two-dimensional fees'];
|
||||
const ITEMS = ['Representative contracts', 'Transaction binding and replay', 'Wallet fee estimation', 'Indexing', 'Failed transactions', 'Receipts', 'Application assumptions', 'Differences: block context', 'Differences: randomness', 'Differences: two-dimensional fees'];
|
||||
const PLANNED = {
|
||||
'Representative contracts': ['an ERC-20 deploys (CREATE address, code at the address)', 'ERC-20 transfer: balances, the Transfer log, eth_call', 'ERC-20 approve and transferFrom', 'a probe contract deploys', 'a counter increments and persists'],
|
||||
'Wallet fee estimation': ['eth_gasPrice, eth_maxPriorityFeePerGas, eth_feeHistory shapes', 'a transfer: estimateGas against gasUsed', 'a contract call: estimateGas against gasUsed', 'the documented difference: a transfer sent with a local 21,000 limit'],
|
||||
|
|
@ -27,14 +27,14 @@ let md = `# Compatibility, measured
|
|||
|
||||
A GPU-secured network for Ethereum-compatible applications and verifiable computation. Compatibility is a product deliverable here, not a sentence: every row below is a test that ran against the devnet from the repository (\`tools/reference-apps/compat/run.mjs\`), with its evidence, and a row that could not run says so and why. Devnet, no value.
|
||||
|
||||
Last run: ${when}${res.meta.network ? ` on ${res.meta.network} (chain id ${res.meta.chain_id})` : ''}. ${res.summary.passed} passed, ${res.summary.failed} failed, ${res.summary.untested} untested. Sender ${res.meta.sender ? res.meta.sender.slice(0, 10) + '…' : 'none'}${res.meta.solc ? `, contracts compiled with solc ${res.meta.solc}` : ''}.
|
||||
Last run: ${when}${res.meta.network ? ` on ${res.meta.network} (chain id ${res.meta.chain_id})` : ''}${res.meta.run_id ? `, run id ${res.meta.run_id}` : ''}. ${res.summary.passed} passed, ${res.summary.failed} failed, ${res.summary.untested} untested. Sender ${res.meta.sender ? res.meta.sender.slice(0, 10) + '…' : 'none'}${res.meta.solc ? `, contracts compiled with solc ${res.meta.solc}` : ''}.
|
||||
|
||||
Three boundaries hold for everything on this page: proven execution is not finality; EVM compatibility is not Ethereum security; ZK is not privacy. The four words included, executed, proven and finalised are defined on [/receipt](/receipt).
|
||||
|
||||
`;
|
||||
for (const item of ITEMS) {
|
||||
const rows = res.rows.filter(r => r.item === item);
|
||||
md += `## ${item}\n\n| Row | Verdict | What was measured | Evidence |\n|---|---|---|---|\n`;
|
||||
const rc = rows[0] && rows[0].register_case; md += `## ${item}${rc ? ` (register case ${rc})` : ''}\n\n| Row | Verdict | What was measured | Evidence |\n|---|---|---|---|\n`;
|
||||
if (rows.length) for (const r of rows) md += `| ${esc(r.name)} | **${r.verdict}** | ${esc(r.detail || '')} | ${esc(evidence(r))} |\n`;
|
||||
else for (const name of PLANNED[item] || []) md += `| ${esc(name)} | **untested** | not run yet: the devnet was switched off when this page was built; the row runs on the 2.0 devnet | |\n`;
|
||||
md += '\n';
|
||||
|
|
|
|||
|
|
@ -1,98 +1,105 @@
|
|||
{
|
||||
"meta": {
|
||||
"run_id": "ra-20261008T1744-evm",
|
||||
"rpc": "https://rpc.devnet.igneum.network",
|
||||
"chain_id": 4465,
|
||||
"network": "igneum-devnet-4",
|
||||
"node_version": "igneumd 2.0.0",
|
||||
"started": "2026-10-08T16:33:17.064Z",
|
||||
"started": "2026-10-08T17:44:37.324Z",
|
||||
"sender": "0x27C433aEec28f9Be4cCe17A8C645d65baDfCF891",
|
||||
"funded": true,
|
||||
"solc": "0.8.28+commit.7893614a.Emscripten.clang",
|
||||
"finished": "2026-10-08T16:34:23.613Z"
|
||||
"finished": "2026-10-08T17:45:51.564Z"
|
||||
},
|
||||
"summary": {
|
||||
"passed": 20,
|
||||
"passed": 22,
|
||||
"failed": 0,
|
||||
"untested": 0
|
||||
},
|
||||
"deployed": {
|
||||
"token": "0xb2495fd85f54d73d7a13059a8e61956b67ad7aed",
|
||||
"probe": "0x3efc60794d574627eb5cb4eca77d96735dac4c42",
|
||||
"token": "0xd93f5e2091ccd544757a38ecf5e4fa176c141b62",
|
||||
"probe": "0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d",
|
||||
"contextTx": {
|
||||
"hash": "0x3c1d2dffad903da6fc821598c7105e8683ea2000a885b440827c73d9dd204fcd",
|
||||
"block": 768,
|
||||
"coinbase": "0xc119093F38E3764Fdd86ea9635b7f616DEC6C3A6",
|
||||
"prevrandao": "9cf17fa10c95c0a8fcb6332bf7cd9dac1415aea7fcf09ca4f13f410fbdc72024"
|
||||
"hash": "0x186def3bfbc8cb0a05e7533d7527c50f91bf034bd94db812cb93a9938b51a228",
|
||||
"block": 3053,
|
||||
"coinbase": "0x1450a9d804D29084d34b93afB001dE807Aa9920d",
|
||||
"prevrandao": "3b9abd95a495129679ad25afa750aff9ec2d03999ed57582698e3f4c4f4db862"
|
||||
}
|
||||
},
|
||||
"rows": [
|
||||
{
|
||||
"id": "contracts.erc20.deploy",
|
||||
"item": "Representative contracts",
|
||||
"register_case": "EVM-05",
|
||||
"name": "an ERC-20 deploys (CREATE address as Ethereum computes it, code at the address)",
|
||||
"verdict": "passed",
|
||||
"detail": "at 0xb2495fd85f54d73d7a13059a8e61956b67ad7aed, block 739, gas 436751 of 639078 estimated",
|
||||
"detail": "at 0xd93f5e2091ccd544757a38ecf5e4fa176c141b62, block 3016, gas 436751 of 639078 estimated",
|
||||
"evidence": {
|
||||
"tx": "0x8fd280cb68049a077b762d0152cace0428230d0757fbed7b05f09a8c24a7a5b9",
|
||||
"address": "0xb2495fd85f54d73d7a13059a8e61956b67ad7aed",
|
||||
"block": 739,
|
||||
"tx": "0x314c059b7e3b8e1b0ec0f9b48430ac8e8a4f71231fbea5dd4caefbc817fea679",
|
||||
"address": "0xd93f5e2091ccd544757a38ecf5e4fa176c141b62",
|
||||
"block": 3016,
|
||||
"gasUsed": 436751,
|
||||
"estimated": 639078
|
||||
},
|
||||
"at": "2026-10-08T16:33:22.753Z"
|
||||
"at": "2026-10-08T17:44:42.491Z"
|
||||
},
|
||||
{
|
||||
"id": "contracts.erc20.transfer",
|
||||
"item": "Representative contracts",
|
||||
"register_case": "EVM-05",
|
||||
"name": "ERC-20 transfer: balances move, the Transfer log decodes, eth_call reads the new balance",
|
||||
"verdict": "passed",
|
||||
"detail": "5 CTT to 0x00000000, log decoded, balance read",
|
||||
"evidence": {
|
||||
"tx": "0xe787770a18a4da1288080e493a5ac3f02aba5f4f5d89302a409e0c2ffc7125fb",
|
||||
"block": 742
|
||||
"tx": "0xd8dbd0346f81f2ef54333c52f64e04cc042c71c291a3ffb602efe989695915ce",
|
||||
"block": 3018
|
||||
},
|
||||
"at": "2026-10-08T16:33:27.426Z"
|
||||
"at": "2026-10-08T17:44:47.181Z"
|
||||
},
|
||||
{
|
||||
"id": "contracts.erc20.approve",
|
||||
"item": "Representative contracts",
|
||||
"register_case": "EVM-05",
|
||||
"name": "ERC-20 approve then transferFrom by a spender (the sender approves itself as spender): allowance consumed, logs decode",
|
||||
"verdict": "passed",
|
||||
"detail": "allowance 3 then 1 after transferFrom of 2",
|
||||
"evidence": {
|
||||
"approve": "0xfc7a3a64b8de2d3b5c75c54036ac5e785df5e1d78cfd6ea9be7602e7ee815d24",
|
||||
"transferFrom": "0x6c0a22dc3511c4a6836f0e9637f52fd3d282549eb5317e3e9078e6db652cef22"
|
||||
"approve": "0x570bbe8618caabfc5fc06b05147c81fdb8858bb80fc1fd525bea6500822752c8",
|
||||
"transferFrom": "0xe6031b34c1b277a753f50f00d656d0c158df29d72c7f0629817115362fb0cd5f"
|
||||
},
|
||||
"at": "2026-10-08T16:33:36.609Z"
|
||||
"at": "2026-10-08T17:44:56.275Z"
|
||||
},
|
||||
{
|
||||
"id": "contracts.probe.deploy",
|
||||
"item": "Representative contracts",
|
||||
"register_case": "EVM-05",
|
||||
"name": "a probe contract deploys (counter, block context reader, revert, out-of-gas loop, CREATE2)",
|
||||
"verdict": "passed",
|
||||
"detail": "at 0x3efc60794d574627eb5cb4eca77d96735dac4c42",
|
||||
"detail": "at 0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d",
|
||||
"evidence": {
|
||||
"tx": "0x551c24030022ebca96ba6d44c6d8d50637cba25be5c7c13839e01eadc6938e15",
|
||||
"address": "0x3efc60794d574627eb5cb4eca77d96735dac4c42",
|
||||
"block": 751
|
||||
"tx": "0x97e39c81a2351c52a3b132f6dd69adcb8a2a83c5185963d63ab8d7a530eebdfa",
|
||||
"address": "0xc4de4cf5766650a5c029fa908e3eb9270a19ed7d",
|
||||
"block": 3026
|
||||
},
|
||||
"at": "2026-10-08T16:33:41.134Z"
|
||||
"at": "2026-10-08T17:45:00.836Z"
|
||||
},
|
||||
{
|
||||
"id": "contracts.counter",
|
||||
"item": "Representative contracts",
|
||||
"register_case": "EVM-05",
|
||||
"name": "a counter increments twice and reads 2 (state persists across chain blocks)",
|
||||
"verdict": "passed",
|
||||
"detail": "count 2 after blocks 753 and 755",
|
||||
"detail": "count 2 after blocks 3029 and 3032",
|
||||
"evidence": {
|
||||
"first": "0xfaf855760dad78fd488432f54db35ecf7baf089408abb37309d77089a99b75a1",
|
||||
"second": "0x97ca41517b1ee4cff94b854308a6ab474be1d6f87eff582837caf89b58fe19c8"
|
||||
"first": "0x1771f03c79e1821270c5a57aeb5f6b1803676310301b742a139a703c6e4fcd3a",
|
||||
"second": "0xad3f659436b7c670c0bc3809871fed8052c3db54c5324f05fdadb19cab05c013"
|
||||
},
|
||||
"at": "2026-10-08T16:33:50.383Z"
|
||||
"at": "2026-10-08T17:45:14.114Z"
|
||||
},
|
||||
{
|
||||
"id": "fees.quotes",
|
||||
"item": "Wallet fee estimation",
|
||||
"register_case": "EVM-03",
|
||||
"name": "eth_gasPrice, eth_maxPriorityFeePerGas and eth_feeHistory answer in Ethereum's shapes",
|
||||
"verdict": "passed",
|
||||
"detail": "gasPrice 196.130804856 gwei, priority 1 gwei, feeHistory 5 base fees",
|
||||
|
|
@ -109,9 +116,9 @@
|
|||
],
|
||||
"gasUsedRatio": [
|
||||
0,
|
||||
0.0014490333333333333,
|
||||
0,
|
||||
0.0008790333333333334
|
||||
0.0008790333333333334,
|
||||
0
|
||||
],
|
||||
"igneum": {
|
||||
"provingBaseFeePerGas": [
|
||||
|
|
@ -121,7 +128,7 @@
|
|||
"0x9184e72a000"
|
||||
]
|
||||
},
|
||||
"oldestBlock": "0x2f0",
|
||||
"oldestBlock": "0xbd6",
|
||||
"reward": [
|
||||
[
|
||||
"0x3b9aca00"
|
||||
|
|
@ -138,86 +145,126 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"at": "2026-10-08T16:33:50.543Z"
|
||||
"at": "2026-10-08T17:45:14.223Z"
|
||||
},
|
||||
{
|
||||
"id": "fees.estimate.transfer",
|
||||
"item": "Wallet fee estimation",
|
||||
"register_case": "EVM-03",
|
||||
"name": "a plain transfer: eth_estimateGas against the receipt's gasUsed (the quote folds proving gas; both above Ethereum's 21,000)",
|
||||
"verdict": "passed",
|
||||
"detail": "estimated 59650, used 21000 (35% of the estimate); Ethereum's figure is 21,000",
|
||||
"evidence": {
|
||||
"tx": "0x99c4dc1ae8d2f32bcc62ed78ff9c829a6281065ba0c9eaf43ffa574c48b1cad0",
|
||||
"tx": "0x6e67cf099e06ee7045d3ea1b890f10f7ce071d47086fe18449285d8a3966c795",
|
||||
"estimated": 59650,
|
||||
"gasUsed": 21000,
|
||||
"ethereum": 21000
|
||||
},
|
||||
"at": "2026-10-08T16:33:55.347Z"
|
||||
"at": "2026-10-08T17:45:18.898Z"
|
||||
},
|
||||
{
|
||||
"id": "fees.estimate.call",
|
||||
"item": "Wallet fee estimation",
|
||||
"register_case": "EVM-03",
|
||||
"name": "a contract call: eth_estimateGas against gasUsed (a wallet that quotes from the node is covered)",
|
||||
"verdict": "passed",
|
||||
"detail": "estimated 134366, used 26371",
|
||||
"evidence": {
|
||||
"tx": "0xeb8bafa3a979a9ccd5c21ca7df91f5eb49c09b926cfa27981fcd38b796b7bee4",
|
||||
"tx": "0xc14d3a767d124d3628d0ae5161b2c2d3bd4433195857d3444c67a056e7c131c6",
|
||||
"estimated": 134366,
|
||||
"gasUsed": 26371
|
||||
},
|
||||
"at": "2026-10-08T16:34:00.085Z"
|
||||
"at": "2026-10-08T17:45:23.595Z"
|
||||
},
|
||||
{
|
||||
"id": "fees.local21000",
|
||||
"item": "Wallet fee estimation",
|
||||
"register_case": "EVM-03",
|
||||
"name": "the documented difference: a transfer sent with a local 21,000 gas limit (Ethereum's intrinsic cost) instead of the node's quote",
|
||||
"verdict": "passed",
|
||||
"detail": "the node accepted it and it succeeded with gasUsed 21000: a wallet with a hard-coded 21,000 works",
|
||||
"evidence": {
|
||||
"sent": true,
|
||||
"hash": "0x538fdf72a8fef37dc1c0024bcfe61028ed74b02c504e9df26978f4adb907b121",
|
||||
"hash": "0x1581f6088e6642cde28dc04ffc0017a0c0a0377e482279c59de9cf46e9022c32",
|
||||
"status": "success",
|
||||
"gasUsed": 21000
|
||||
},
|
||||
"at": "2026-10-08T16:34:04.657Z"
|
||||
"at": "2026-10-08T17:45:27.957Z"
|
||||
},
|
||||
{
|
||||
"id": "failed.revert",
|
||||
"item": "Failed transactions",
|
||||
"register_case": "EVM-05",
|
||||
"name": "a revert with a reason: eth_call returns the reason, the sent transaction has status 0, gas is charged, the nonce advances",
|
||||
"verdict": "passed",
|
||||
"detail": "eth_call answers code 3 \"execution reverted\" with Error(string) data decoding to \"compat says no\"; the sent transaction: status 0, gasUsed 22292 of 200000, nonce 23 to 24",
|
||||
"detail": "eth_call answers code 3 \"execution reverted\" with Error(string) data decoding to \"compat says no\"; the sent transaction: status 0, gasUsed 22292 of 200000, nonce 37 to 38",
|
||||
"evidence": {
|
||||
"tx": "0xfcdf8d24eb414154d24cf3e0c11d5d732ba3f612e219d8adc316ec6323a06a1e",
|
||||
"tx": "0x90e50327309da43b7aab7a39c9c5970784bbc0476010d29ef0d65ce1bfc81344",
|
||||
"gasUsed": 22292,
|
||||
"block": 762,
|
||||
"block": 3044,
|
||||
"call_error_code": 3,
|
||||
"call_error_message": "execution reverted",
|
||||
"revert_data": "0x08c379a00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000e636f6d7061742073617973206e6f000000000000000000000000000000000000"
|
||||
},
|
||||
"at": "2026-10-08T16:34:09.064Z"
|
||||
"at": "2026-10-08T17:45:32.415Z"
|
||||
},
|
||||
{
|
||||
"id": "failed.outofgas",
|
||||
"item": "Failed transactions",
|
||||
"register_case": "EVM-05",
|
||||
"name": "an out-of-gas call: status 0 and the whole limit charged",
|
||||
"verdict": "passed",
|
||||
"detail": "status 0, gasUsed 150000 of the 150000 limit",
|
||||
"evidence": {
|
||||
"tx": "0xb3986b45642c28dd029dc34714878e42c025a0bd6349609900b7cef120f7a1f6",
|
||||
"tx": "0x4d13892063cc12ce5b67209f0a8c1dfa438fcc54e916c6748a556220f744e5f0",
|
||||
"gasUsed": 150000,
|
||||
"limit": 150000
|
||||
},
|
||||
"at": "2026-10-08T16:34:13.537Z"
|
||||
"at": "2026-10-08T17:45:36.717Z"
|
||||
},
|
||||
{
|
||||
"id": "binding.chainid",
|
||||
"item": "Transaction binding and replay",
|
||||
"register_case": "EVM-02",
|
||||
"name": "a transaction signed for another chain id (the previous devnet's 4464) is refused by the node",
|
||||
"verdict": "passed",
|
||||
"detail": "refused: wrong chain id: expected 4465, got Some(4464)",
|
||||
"evidence": {
|
||||
"chain_id_signed": 4464,
|
||||
"error": {
|
||||
"code": -32000,
|
||||
"message": "wrong chain id: expected 4465, got Some(4464)"
|
||||
}
|
||||
},
|
||||
"at": "2026-10-08T17:45:36.791Z"
|
||||
},
|
||||
{
|
||||
"id": "binding.replay",
|
||||
"item": "Transaction binding and replay",
|
||||
"register_case": "EVM-02",
|
||||
"name": "the same signed transaction sent twice: the second send is refused (nonce already used)",
|
||||
"verdict": "passed",
|
||||
"detail": "first send mined in block 3049; the replay refused: nonce too low: account nonce is 40, transaction has 39",
|
||||
"evidence": {
|
||||
"tx": "0xb1722ee04bcf1aa0ff1c7a76aaaf5c42902bcb32aeeb2d463c81922ce8f5a36f",
|
||||
"block": 3049,
|
||||
"replay": {
|
||||
"code": -32000,
|
||||
"message": "nonce too low: account nonce is 40, transaction has 39"
|
||||
},
|
||||
"nonce_after": 40
|
||||
},
|
||||
"at": "2026-10-08T17:45:41.296Z"
|
||||
},
|
||||
{
|
||||
"id": "receipts.fields",
|
||||
"item": "Receipts",
|
||||
"register_case": "EVM-06",
|
||||
"name": "a receipt carries every Ethereum field, logsBloom recomputes from its logs, contractAddress is set on creation",
|
||||
"verdict": "passed",
|
||||
"detail": "15 fields, bloom recomputed over 1 log(s), contractAddress 0xb2495fd8",
|
||||
"detail": "15 fields, bloom recomputed over 1 log(s), contractAddress 0xd93f5e20",
|
||||
"evidence": {
|
||||
"tx": "0x8fd280cb68049a077b762d0152cace0428230d0757fbed7b05f09a8c24a7a5b9",
|
||||
"tx": "0x314c059b7e3b8e1b0ec0f9b48430ac8e8a4f71231fbea5dd4caefbc817fea679",
|
||||
"fields": [
|
||||
"blockHash",
|
||||
"blockNumber",
|
||||
|
|
@ -236,134 +283,141 @@
|
|||
"type"
|
||||
]
|
||||
},
|
||||
"at": "2026-10-08T16:34:13.578Z"
|
||||
"at": "2026-10-08T17:45:41.335Z"
|
||||
},
|
||||
{
|
||||
"id": "receipts.root",
|
||||
"item": "Receipts",
|
||||
"register_case": "EVM-06",
|
||||
"name": "the block's receipts rebuild its receipts trie root (Ethereum's layout, the node's encoding) equal to the header's receiptsRoot",
|
||||
"verdict": "passed",
|
||||
"detail": "block 742, 1 receipts, root 0xa28061003c4c",
|
||||
"detail": "block 3018, 1 receipts, root 0xc443c3e57c60",
|
||||
"evidence": {
|
||||
"block": 742,
|
||||
"block": 3018,
|
||||
"receipts": 1,
|
||||
"receiptsRoot": "0xa28061003c4cfa70006e203524ededd4668753d432bef4c657c1ede124d6ac55"
|
||||
"receiptsRoot": "0xc443c3e57c6002f379352b6c2897b6ea03a4320255831fb502ffc3094b888836"
|
||||
},
|
||||
"at": "2026-10-08T16:34:13.651Z"
|
||||
"at": "2026-10-08T17:45:41.411Z"
|
||||
},
|
||||
{
|
||||
"id": "indexing.logs",
|
||||
"item": "Indexing",
|
||||
"register_case": "EVM-06",
|
||||
"name": "eth_getLogs by address, by topic and by block range returns the receipts' logs with consistent logIndex and transactionIndex",
|
||||
"verdict": "passed",
|
||||
"detail": "4 logs by address, 3 Transfer logs by topic, each found in its receipt",
|
||||
"evidence": {
|
||||
"byAddress": 4,
|
||||
"byTopic": 3,
|
||||
"fromBlock": 739
|
||||
"fromBlock": 3016
|
||||
},
|
||||
"at": "2026-10-08T16:34:13.880Z"
|
||||
"at": "2026-10-08T17:45:41.709Z"
|
||||
},
|
||||
{
|
||||
"id": "indexing.block",
|
||||
"item": "Indexing",
|
||||
"register_case": "EVM-06",
|
||||
"name": "eth_getBlockByNumber with full transactions, eth_getBlockReceipts and eth_getTransactionByHash agree on indices and hashes",
|
||||
"verdict": "passed",
|
||||
"detail": "block 742: 1 transactions, indices 0 to 0 continuous",
|
||||
"detail": "block 3018: 1 transactions, indices 0 to 0 continuous",
|
||||
"evidence": {
|
||||
"block": 742,
|
||||
"block": 3018,
|
||||
"count": 1
|
||||
},
|
||||
"at": "2026-10-08T16:34:14.059Z"
|
||||
"at": "2026-10-08T17:45:41.834Z"
|
||||
},
|
||||
{
|
||||
"id": "assumptions.context",
|
||||
"item": "Application assumptions",
|
||||
"register_case": "EVM-04",
|
||||
"name": "block.number, timestamp, chainid, basefee and blockhash(number-1) read from a contract match the block the call executed in",
|
||||
"verdict": "passed",
|
||||
"detail": "block 768: number, timestamp, chainid 4465, basefee, blockhash(n-1) = parentHash, msg.sender and tx.origin all match",
|
||||
"detail": "block 3053: number, timestamp, chainid 4465, basefee, blockhash(n-1) = parentHash, msg.sender and tx.origin all match",
|
||||
"evidence": {
|
||||
"tx": "0x3c1d2dffad903da6fc821598c7105e8683ea2000a885b440827c73d9dd204fcd",
|
||||
"block": 768
|
||||
"tx": "0x186def3bfbc8cb0a05e7533d7527c50f91bf034bd94db812cb93a9938b51a228",
|
||||
"block": 3053
|
||||
},
|
||||
"at": "2026-10-08T16:34:18.585Z"
|
||||
"at": "2026-10-08T17:45:46.595Z"
|
||||
},
|
||||
{
|
||||
"id": "assumptions.create2",
|
||||
"item": "Application assumptions",
|
||||
"register_case": "EVM-04",
|
||||
"name": "CREATE2 lands at the address Ethereum computes (keccak(0xff, deployer, salt, keccak(initcode)))",
|
||||
"verdict": "passed",
|
||||
"detail": "child at 0xE63036B0fEC0d01321Ef4DA62C084d1Ec2eA47be",
|
||||
"detail": "child at 0xe3bC69915FB7318236c5ff31EF679aF6ae5b8699",
|
||||
"evidence": {
|
||||
"tx": "0x11cd98733996949be674b9669a3d459ba7bbeda8345fa2879ecb9fe36b9aa80c",
|
||||
"address": "0xE63036B0fEC0d01321Ef4DA62C084d1Ec2eA47be"
|
||||
"tx": "0x442569b23f8b35526f48016154f887c77478029d977742e53bf192ac19eeaf11",
|
||||
"address": "0xe3bC69915FB7318236c5ff31EF679aF6ae5b8699"
|
||||
},
|
||||
"at": "2026-10-08T16:34:23.161Z"
|
||||
"at": "2026-10-08T17:45:51.218Z"
|
||||
},
|
||||
{
|
||||
"id": "difference.coinbase",
|
||||
"item": "Differences: block context",
|
||||
"register_case": "EVM-04",
|
||||
"name": "block.coinbase is the miner of the DAG block that first included the transaction, not one miner per chain block (measured: coinbase against the block's igneum.mergeset)",
|
||||
"verdict": "passed",
|
||||
"detail": "coinbase 0xc119093F = the including block's miner 0xc119093f; the chain block merged 2 block(s) with 1 distinct miner(s), chain block miner 0xc119093f",
|
||||
"detail": "coinbase 0x1450a9d8 = the including block's miner 0x1450a9d8; the chain block merged 1 block(s) with 1 distinct miner(s), chain block miner 0x1450a9d8",
|
||||
"evidence": {
|
||||
"tx": "0x3c1d2dffad903da6fc821598c7105e8683ea2000a885b440827c73d9dd204fcd",
|
||||
"coinbase": "0xc119093F38E3764Fdd86ea9635b7f616DEC6C3A6",
|
||||
"includingMiner": "0xc119093f38e3764fdd86ea9635b7f616dec6c3a6",
|
||||
"tx": "0x186def3bfbc8cb0a05e7533d7527c50f91bf034bd94db812cb93a9938b51a228",
|
||||
"coinbase": "0x1450a9d804D29084d34b93afB001dE807Aa9920d",
|
||||
"includingMiner": "0x1450a9d804d29084d34b93afb001de807aa9920d",
|
||||
"mergesetMiners": [
|
||||
"0xc119093f38e3764fdd86ea9635b7f616dec6c3a6",
|
||||
"0xc119093f38e3764fdd86ea9635b7f616dec6c3a6"
|
||||
"0x1450a9d804d29084d34b93afb001de807aa9920d"
|
||||
],
|
||||
"chainBlockMiner": "0xc119093f38e3764fdd86ea9635b7f616dec6c3a6"
|
||||
"chainBlockMiner": "0x1450a9d804d29084d34b93afb001de807aa9920d"
|
||||
},
|
||||
"at": "2026-10-08T16:34:23.244Z"
|
||||
"at": "2026-10-08T17:45:51.299Z"
|
||||
},
|
||||
{
|
||||
"id": "difference.randomness",
|
||||
"item": "Differences: randomness",
|
||||
"register_case": "EVM-04",
|
||||
"name": "block.prevrandao: its value is derived from the chain's epoch seed (not Ethereum's beacon RANDAO); measured across consecutive blocks, and from a contract when a sender is funded",
|
||||
"verdict": "passed",
|
||||
"detail": "blocks 767 to 770: 1 distinct epoch seed(s), 4 distinct mixHash value(s) in the headers; the source is the chain's epoch seed, which holds for an epoch, so prevrandao is not fresh per block and must not seed a lottery; a contract reading it twice in one call got the same value, and at block 768 it read 0x9cf17fa10c95…",
|
||||
"detail": "blocks 3051 to 3054: 1 distinct epoch seed(s), 4 distinct mixHash value(s) in the headers; the source is the chain's epoch seed, which holds for an epoch, so prevrandao is not fresh per block and must not seed a lottery; a contract reading it twice in one call got the same value, and at block 3053 it read 0x3b9abd95a495…",
|
||||
"evidence": {
|
||||
"blocks": [
|
||||
{
|
||||
"number": 767,
|
||||
"mixHash": "0xe5baec105ee8d299d423ae632944b4b185d4d885e57088b829443202c36d82b5",
|
||||
"epochSeed": "0x7c36b83374a673e990213be462509dcb08bcfc85144306aa9517cf3ad66faf6e"
|
||||
"number": 3051,
|
||||
"mixHash": "0x9855c45455bb5481bce8e7326bde56289d8455fac3ba088c09852cbc27f83a4d",
|
||||
"epochSeed": "0xc0e88f47b504f36aa8310a2e0735cf6c48a85a00e10349cbc16420ded775d186"
|
||||
},
|
||||
{
|
||||
"number": 768,
|
||||
"mixHash": "0x9cf17fa10c95c0a8fcb6332bf7cd9dac1415aea7fcf09ca4f13f410fbdc72024",
|
||||
"epochSeed": "0x7c36b83374a673e990213be462509dcb08bcfc85144306aa9517cf3ad66faf6e"
|
||||
"number": 3052,
|
||||
"mixHash": "0x89c614f9b0eb3acaec820886309da7c17d92bddd467c12d2341b5d255c78d3be",
|
||||
"epochSeed": "0xc0e88f47b504f36aa8310a2e0735cf6c48a85a00e10349cbc16420ded775d186"
|
||||
},
|
||||
{
|
||||
"number": 769,
|
||||
"mixHash": "0xe39ab8de805c6b8c8ccad7b078a788b6f2b79d7b25e19f0211951a1aba5e3c42",
|
||||
"epochSeed": "0x7c36b83374a673e990213be462509dcb08bcfc85144306aa9517cf3ad66faf6e"
|
||||
"number": 3053,
|
||||
"mixHash": "0x3b9abd95a495129679ad25afa750aff9ec2d03999ed57582698e3f4c4f4db862",
|
||||
"epochSeed": "0xc0e88f47b504f36aa8310a2e0735cf6c48a85a00e10349cbc16420ded775d186"
|
||||
},
|
||||
{
|
||||
"number": 770,
|
||||
"mixHash": "0xc749632e2ede6ff39f5e4ac3549a59d701509803d011311a9b1da9e4c363eebd",
|
||||
"epochSeed": "0x7c36b83374a673e990213be462509dcb08bcfc85144306aa9517cf3ad66faf6e"
|
||||
"number": 3054,
|
||||
"mixHash": "0xea8435d29cf98ef613ad6a526ee0a398158a68de04f855b296ce7d82187c57c2",
|
||||
"epochSeed": "0xc0e88f47b504f36aa8310a2e0735cf6c48a85a00e10349cbc16420ded775d186"
|
||||
}
|
||||
],
|
||||
"contract": {
|
||||
"hash": "0x3c1d2dffad903da6fc821598c7105e8683ea2000a885b440827c73d9dd204fcd",
|
||||
"block": 768,
|
||||
"coinbase": "0xc119093F38E3764Fdd86ea9635b7f616DEC6C3A6",
|
||||
"prevrandao": "9cf17fa10c95c0a8fcb6332bf7cd9dac1415aea7fcf09ca4f13f410fbdc72024"
|
||||
"hash": "0x186def3bfbc8cb0a05e7533d7527c50f91bf034bd94db812cb93a9938b51a228",
|
||||
"block": 3053,
|
||||
"coinbase": "0x1450a9d804D29084d34b93afB001dE807Aa9920d",
|
||||
"prevrandao": "3b9abd95a495129679ad25afa750aff9ec2d03999ed57582698e3f4c4f4db862"
|
||||
}
|
||||
},
|
||||
"at": "2026-10-08T16:34:23.576Z"
|
||||
"at": "2026-10-08T17:45:51.526Z"
|
||||
},
|
||||
{
|
||||
"id": "difference.fees",
|
||||
"item": "Differences: two-dimensional fees",
|
||||
"register_case": "EVM-03",
|
||||
"name": "a receipt carries pgasUsed, provingBaseFeePerGas, burnedProvingFee and burnedExecutionBaseFee; effectiveGasPrice covers both dimensions",
|
||||
"verdict": "passed",
|
||||
"detail": "gasUsed 51375, pgasUsed 1968, proving base fee 10000 gwei per pgas; paid 5188875000000000 wei, of which proving burn 19680000000000000, execution burn 5137500000000000, tip 41100000000000 (parts sum differs from gasUsed × effectiveGasPrice)",
|
||||
"evidence": {
|
||||
"tx": "0xe787770a18a4da1288080e493a5ac3f02aba5f4f5d89302a409e0c2ffc7125fb",
|
||||
"tx": "0xd8dbd0346f81f2ef54333c52f64e04cc042c71c291a3ffb602efe989695915ce",
|
||||
"gasUsed": 51375,
|
||||
"pgasUsed": 1968,
|
||||
"effectiveGasPrice": "0x178411b200",
|
||||
|
|
@ -376,7 +430,7 @@
|
|||
"wei": "0x95855b99e00"
|
||||
}
|
||||
],
|
||||
"includingBlock": "0x9eb90f5ec3bf0ef4ddeb220137e756f63ddf74a1deddfffe9fbc44f473bea88b",
|
||||
"includingBlock": "0x2113006670863f5924a156be6b07aedef8889fc57f931d9e986abbd90f9b9dcc",
|
||||
"includingMiner": "0x53fe98022c2ac26d5d721457fb1c374b4d56144b",
|
||||
"minerTip": "0x256156e67800",
|
||||
"overBudget": false,
|
||||
|
|
@ -386,7 +440,7 @@
|
|||
},
|
||||
"parts_equal_paid": false
|
||||
},
|
||||
"at": "2026-10-08T16:34:23.613Z"
|
||||
"at": "2026-10-08T17:45:51.564Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,8 @@ function compile() {
|
|||
// ---- plumbing ---------------------------------------------------------------------------------------------------
|
||||
const rows = []; let deployed = {};
|
||||
async function rpc(method, params = []) { const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method, params }), signal: AbortSignal.timeout(30000) }); const j = await r.json(); if (j.error) throw new Error(`${method}: ${j.error.message}`); return j.result; }
|
||||
function row(id, item, name, verdict, detail, evidence = {}) { rows.push({ id, item, name, verdict, detail, evidence, at: new Date().toISOString() }); console.log(`${uk()} ${verdict.padEnd(8)} ${id} ${name}${detail ? ' :: ' + detail : ''}`); }
|
||||
const CASE = { 'Representative contracts': 'EVM-05', 'Wallet fee estimation': 'EVM-03', 'Indexing': 'EVM-06', 'Failed transactions': 'EVM-05', 'Receipts': 'EVM-06', 'Application assumptions': 'EVM-04', 'Differences: block context': 'EVM-04', 'Differences: randomness': 'EVM-04', 'Differences: two-dimensional fees': 'EVM-03', 'Transaction binding and replay': 'EVM-02' };
|
||||
function row(id, item, name, verdict, detail, evidence = {}) { rows.push({ id, item, register_case: CASE[item] || null, name, verdict, detail, evidence, at: new Date().toISOString() }); console.log(`${uk()} ${verdict.padEnd(8)} ${id} ${name}${detail ? ' :: ' + detail : ''}`); }
|
||||
async function test(id, item, name, fn, needsSender = false) {
|
||||
if (ONLY && !id.startsWith(ONLY)) return;
|
||||
if (needsSender && !funded) return row(id, item, name, 'untested', 'no funded sender on this devnet tonight (the faucet refused this connection; ask again tomorrow)');
|
||||
|
|
@ -83,7 +84,7 @@ async function send(tx) { // sends with the node's own gas estimate (the two-dim
|
|||
return { hash, gas, r };
|
||||
}
|
||||
const net = await rpc('igneum_getNodeInfo').catch(() => null);
|
||||
const meta = { rpc: RPC, chain_id: CHAIN_ID, network: net && net.network, node_version: net && net.version, started: new Date().toISOString(), sender: account ? account.address : null, funded };
|
||||
const meta = { run_id: 'ra-' + new Date().toISOString().replace(/[-:]/g, '').slice(0, 13) + '-evm', rpc: RPC, chain_id: CHAIN_ID, network: net && net.network, node_version: net && net.version, started: new Date().toISOString(), sender: account ? account.address : null, funded };
|
||||
const C = compile(); meta.solc = C.solc;
|
||||
|
||||
// ---- 1 representative contracts ---------------------------------------------------------------------------------
|
||||
|
|
@ -195,6 +196,29 @@ async function liveBlockWithTxs() {
|
|||
for (let n = tip; n > Math.max(0, tip - 600); n--) { const b = await rpc('eth_getBlockByNumber', ['0x' + n.toString(16), false]); if (b && b.transactions.length >= 2) return b.number; }
|
||||
throw Object.assign(new Error('no block with two or more transactions in the last 600 chain blocks (the chain is young and nothing sends yet); reruns when the chain carries transactions'), { untested: true });
|
||||
}
|
||||
// ---- EVM-02 transaction binding and replay protection ----------------------------------------------------------------
|
||||
await test('binding.chainid', 'Transaction binding and replay', 'a transaction signed for another chain id (the previous devnet\'s 4464) is refused by the node', async () => {
|
||||
const { serializeTransaction, parseGwei } = await import('viem');
|
||||
const nonce = await pub.getTransactionCount({ address: account.address });
|
||||
const tx = { chainId: CHAIN_ID - 1, nonce, to: other, value: 1n, gas: 60000n, maxFeePerGas: parseGwei('300'), maxPriorityFeePerGas: parseGwei('1'), type: 'eip1559' };
|
||||
const signed = await account.signTransaction(tx);
|
||||
const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_sendRawTransaction', params: [signed] }) }).then(x => x.json());
|
||||
if (!r.error) throw new Error(`the node ACCEPTED a transaction signed for chain id ${CHAIN_ID - 1}: ${r.result}`);
|
||||
return { detail: `refused: ${String(r.error.message).slice(0, 120)}`, evidence: { chain_id_signed: CHAIN_ID - 1, error: r.error } };
|
||||
}, true);
|
||||
await test('binding.replay', 'Transaction binding and replay', 'the same signed transaction sent twice: the second send is refused (nonce already used)', async () => {
|
||||
const { parseGwei } = await import('viem');
|
||||
const nonce = await pub.getTransactionCount({ address: account.address });
|
||||
const gas = await pub.estimateGas({ account, to: other, value: 1n });
|
||||
const signed = await account.signTransaction({ chainId: CHAIN_ID, nonce, to: other, value: 1n, gas, maxFeePerGas: parseGwei('300'), maxPriorityFeePerGas: parseGwei('1'), type: 'eip1559' });
|
||||
const first = await pub.sendRawTransaction({ serializedTransaction: signed });
|
||||
const rc = await pub.waitForTransactionReceipt({ hash: first, timeout: 180000 });
|
||||
const again = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_sendRawTransaction', params: [signed] }) }).then(x => x.json());
|
||||
const later = await pub.getTransactionCount({ address: account.address });
|
||||
if (!again.error && later !== nonce + 1) throw new Error('the replay was accepted');
|
||||
return { detail: `first send mined in block ${rc.blockNumber}; the replay ${again.error ? 'refused: ' + String(again.error.message).slice(0, 100) : 'answered the same hash and did not execute again (nonce stayed ' + later + ')'}`, evidence: { tx: first, block: Number(rc.blockNumber), replay: again.error || again.result, nonce_after: later } };
|
||||
}, true);
|
||||
|
||||
// ---- 5 receipts, 3 indexing ---------------------------------------------------------------------------------------
|
||||
await test('receipts.fields', 'Receipts', 'a receipt carries every Ethereum field, logsBloom recomputes from its logs, contractAddress is set on creation', async () => {
|
||||
const r = await rpc('eth_getTransactionReceipt', [rows.find(x => x.id === 'contracts.erc20.deploy').evidence.tx]);
|
||||
|
|
|
|||
1
tools/reference-apps/fixtures/dn4/checkpoint.json
Normal file
1
tools/reference-apps/fixtures/dn4/checkpoint.json
Normal file
File diff suppressed because one or more lines are too long
1
tools/reference-apps/fixtures/dn4/receipt.json
Normal file
1
tools/reference-apps/fixtures/dn4/receipt.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -27,6 +27,8 @@ const WRPC = process.env.WRPC || 'ws://127.0.0.1:28880';
|
|||
const CHECKPOINT_URL = process.env.CHECKPOINT_URL || 'https://igneum.network/api/checkpoint?source=dn4';
|
||||
const SOURCE = process.env.CHECKPOINT_SOURCE || 'dn4'; // the observer's table prefix for the devnet the reader follows (dn3, dn4)
|
||||
const CHAIN_ID_NAME = process.env.CHAIN_ID_NAME || 'igneum-devnet-4';
|
||||
// the amount's wire width in a transaction's hashing: 8 bytes at 8 decimals (Devnet 3), 16 at 18 decimals (the 2.0 devnet, consensus/core/src/unit.rs amount_wire_len)
|
||||
const AMOUNT_WIRE_LEN = Number(process.env.AMOUNT_WIRE_LEN || (CHAIN_ID_NAME === 'igneum-devnet-3' ? 8 : 16));
|
||||
const SEGMENT_BLOCKS = 8;
|
||||
const strip = s => String(s).replace(/^0x/i, '').toLowerCase();
|
||||
const log = (...a) => console.log(new Date().toISOString(), ...a);
|
||||
|
|
@ -178,7 +180,7 @@ async function balance(q) {
|
|||
const carrier = await body(chosen.carrier);
|
||||
const coinbase = carrier.transactions[0];
|
||||
const evmHashes = (carrier.evmTransactions || []).map(raw => keccak_256(hexToBytes(strip(raw))));
|
||||
const leaves = [coinbaseTxHash(coinbase, blake2b), ...evmHashes];
|
||||
const leaves = [coinbaseTxHash(coinbase, blake2b, AMOUNT_WIRE_LEN), ...evmHashes];
|
||||
const root = bytesToHex(merkleRoot(leaves, blake2b));
|
||||
if (root !== strip(carrier.header.hashMerkleRoot)) throw httpError(500, `the carrier's body root recomputes to ${root.slice(0, 12)}, the header says ${strip(carrier.header.hashMerkleRoot).slice(0, 12)}: the serialisation here is wrong, not the chain`);
|
||||
const records = segmentRecordsOf(hexToBytes(strip(coinbase.payload)));
|
||||
|
|
@ -198,7 +200,7 @@ async function balance(q) {
|
|||
checkpoint_certificate: certificate ? { ok: true, ...certificate } : undefined,
|
||||
segment: { first: chosen.first, last: chosen.last, carrier: chosen.carrier, carrier_chain_block: chosen.carrierNumber, aggregator_key_hash: chosen.keyHash, paid_wei: chosen.paidWei, statement: chosen.statement },
|
||||
headers,
|
||||
carrier: { coinbase, evm_tx_hashes: evmHashes.map(bytesToHex), leaf_index: 0, merkle_siblings: siblings(leaves, 0), amount_wire_len: 8 },
|
||||
carrier: { coinbase, evm_tx_hashes: evmHashes.map(bytesToHex), leaf_index: 0, merkle_siblings: siblings(leaves, 0), amount_wire_len: AMOUNT_WIRE_LEN },
|
||||
segment_record_hex: bytesToHex(rec.bytes),
|
||||
account: { ...account, evm_chain_id: Number(chainId) },
|
||||
};
|
||||
|
|
@ -247,7 +249,7 @@ async function receipt(q) {
|
|||
const raws = b.evmTransactions || [];
|
||||
const idx = raws.findIndex(raw => bytesToHex(keccak_256(hexToBytes(strip(raw)))) === strip(tx));
|
||||
if (idx < 0) throw httpError(500, 'the including block\'s body does not carry the transaction');
|
||||
const leaves = [coinbaseTxHash(b.transactions[0], blake2b), ...raws.map(raw => keccak_256(hexToBytes(strip(raw))))];
|
||||
const leaves = [coinbaseTxHash(b.transactions[0], blake2b, AMOUNT_WIRE_LEN), ...raws.map(raw => keccak_256(hexToBytes(strip(raw))))];
|
||||
const root = bytesToHex(merkleRoot(leaves, blake2b));
|
||||
if (root !== strip(b.header.hashMerkleRoot)) throw httpError(500, `the including block's body root recomputes to ${root.slice(0, 12)}, the header says ${strip(b.header.hashMerkleRoot).slice(0, 12)}`);
|
||||
const headers = await headerPath(including, chainNumber, cpNumber, cpHash);
|
||||
|
|
@ -267,7 +269,7 @@ async function receipt(q) {
|
|||
const carrier = await body(c.carrier);
|
||||
const coinbase = carrier.transactions[0];
|
||||
const evmHashes = (carrier.evmTransactions || []).map(raw => keccak_256(hexToBytes(strip(raw))));
|
||||
const leaves = [coinbaseTxHash(coinbase, blake2b), ...evmHashes];
|
||||
const leaves = [coinbaseTxHash(coinbase, blake2b, AMOUNT_WIRE_LEN), ...evmHashes];
|
||||
const rec = segmentRecordsOf(hexToBytes(strip(coinbase.payload))).find(r => Number(r.first) === first && Number(r.last) === last);
|
||||
if (!rec) throw new Error('the carrier\'s coinbase holds no record for the segment');
|
||||
const plan = await exec('igneum_getShardPlan', ['0x' + chainNumber.toString(16)]);
|
||||
|
|
@ -278,7 +280,7 @@ async function receipt(q) {
|
|||
if (shardIndex < 0) throw new Error('the transaction index falls in no shard of the plan');
|
||||
segment = {
|
||||
first, last, record_hex: bytesToHex(rec.bytes), headers: await headerPath(strip(c.carrier), carrierNumber, cpNumber, cpHash),
|
||||
carrier: { coinbase, evm_tx_hashes: evmHashes.map(bytesToHex), leaf_index: 0, merkle_siblings: siblings(leaves, 0), amount_wire_len: 8 },
|
||||
carrier: { coinbase, evm_tx_hashes: evmHashes.map(bytesToHex), leaf_index: 0, merkle_siblings: siblings(leaves, 0), amount_wire_len: AMOUNT_WIRE_LEN },
|
||||
shard_receipts_roots: plan.shards.map(sh => strip(sh.receiptsRoot)), shard_index: shardIndex, receipts: mine, receipt_position: txIndex - start,
|
||||
aggregator_key_hash: c.keyHash, paid_wei: sr.paid.wei,
|
||||
};
|
||||
|
|
|
|||
36
tools/reference-apps/registry-writeback.mjs
Normal file
36
tools/reference-apps/registry-writeback.mjs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Writes a lane's run fields into docs/plans/igneum-2.0-test-registry.json under rule 26: the named cases get run_status,
|
||||
// evidence_path, run_id, updated (and clock, owner_lane when given) inserted into their objects as new lines, and every
|
||||
// other byte of the file stays exactly as master carries it (no re-serialisation: rule 26 compares master's lines).
|
||||
// Refuses a case id the file does not hold. A field the case already carries is replaced on its own line.
|
||||
// node registry-writeback.mjs <registry.json> <updates.json>
|
||||
// updates.json: { "EVM-05": { "run_status": "PASS", "evidence_path": "...", "run_id": "...", "clock": "..." }, ... }
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
const [, , file, updatesFile] = process.argv;
|
||||
if (!file || !updatesFile) { console.error('usage: node registry-writeback.mjs <registry.json> <updates.json>'); process.exit(2); }
|
||||
let text = readFileSync(file, 'utf8');
|
||||
const updates = JSON.parse(readFileSync(updatesFile, 'utf8'));
|
||||
const now = new Date().toISOString();
|
||||
const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
let touched = 0;
|
||||
for (const [id, fields] of Object.entries(updates)) {
|
||||
const m = new RegExp(`^([ \\t]*)"id":\\s*"${esc(id)}",?[ \\t]*\\n`, 'm').exec(text);
|
||||
if (!m) { console.error('case not in the registry: ' + id); process.exit(1); }
|
||||
const indent = m[1]; let at = m.index + m[0].length;
|
||||
// the case object's end: its opening line is the one before the id line; the closing brace sits at that line's indent
|
||||
const openLine = text.lastIndexOf('\n', m.index - 1); const opener = text.slice(text.lastIndexOf('\n', openLine - 1) + 1, openLine);
|
||||
const parentIndent = (opener.match(/^[ \t]*/) || [''])[0];
|
||||
const endRe = new RegExp(`^${parentIndent}\\},?[ \\t]*\\n`, 'mg'); endRe.lastIndex = at;
|
||||
const end = endRe.exec(text); if (!end) { console.error('no object end for ' + id); process.exit(1); }
|
||||
let body = text.slice(at, end.index);
|
||||
const all = { ...fields, updated: now };
|
||||
for (const [k, v] of Object.entries(all)) {
|
||||
const line = `${indent}"${k}": ${JSON.stringify(v)},\n`;
|
||||
const have = new RegExp(`^${indent}"${esc(k)}":.*\\n`, 'm');
|
||||
body = have.test(body) ? body.replace(have, line) : line + body;
|
||||
}
|
||||
text = text.slice(0, at) + body + text.slice(end.index);
|
||||
touched++;
|
||||
}
|
||||
JSON.parse(text); // the result must still parse
|
||||
writeFileSync(file, text);
|
||||
console.log(`inserted run fields into ${touched} case(s) of ${file} at ${now}, master's lines untouched`);
|
||||
28
tools/reference-apps/ver/evidence/VER-01.json
Normal file
28
tools/reference-apps/ver/evidence/VER-01.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"case": "VER-01",
|
||||
"title": "Authenticate light-client bootstrap",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:23.207Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "FAIL",
|
||||
"note": "FAIL by design today: the voter table is a node-supplied trust anchor (disclosed on /light); closing it needs a header commitment to the table or a shipped trust anchor with authority-change tracking (VER-02)",
|
||||
"checks": [
|
||||
{
|
||||
"name": "a genuine certificate verifies",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a certificate with one bit of its signature flipped is refused",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a certificate signed by a fabricated voter table the RPC supplied is REFUSED (the case's acceptance: signatures over a node-supplied table do not by themselves pass)",
|
||||
"ok": false,
|
||||
"detail": "ACCEPTED: the client takes the voter table from the node; the trust anchor is disclosed on the page but authentication does not fail closed"
|
||||
},
|
||||
{
|
||||
"name": "with the bootstrap service removed the client reports an error rather than a trusted-RPC balance",
|
||||
"ok": false
|
||||
}
|
||||
]
|
||||
}
|
||||
33
tools/reference-apps/ver/evidence/VER-02.json
Normal file
33
tools/reference-apps/ver/evidence/VER-02.json
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"case": "VER-02",
|
||||
"title": "Verify evolving authority and execution statements",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:23.402Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "FAIL",
|
||||
"note": "FAIL by design today, disclosed: statement-based (the proof is verified by nodes), table from the node; closing needs an in-browser verifier for the permitted proof and a table commitment",
|
||||
"checks": [
|
||||
{
|
||||
"name": "a genuine balance proof verifies",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a segment record with its post_root altered is refused",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a state root that is not the committed post_root is refused",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "the client verifies the permitted execution proof (SP1) and authenticates its inputs, never an aggregator statement in its place",
|
||||
"ok": false,
|
||||
"detail": "the client takes the aggregator's signed statement; no SP1 verifier runs in the browser (disclosed on /light); the node verifies the proof before paying the record"
|
||||
},
|
||||
{
|
||||
"name": "authority changes between voter tables are authenticated by the client",
|
||||
"ok": false,
|
||||
"detail": "the client holds no table history; each certificate is checked against the table the node supplies at that index"
|
||||
}
|
||||
]
|
||||
}
|
||||
28
tools/reference-apps/ver/evidence/VER-03.json
Normal file
28
tools/reference-apps/ver/evidence/VER-03.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"case": "VER-03",
|
||||
"title": "Prove successful payment rather than inclusion",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:23.752Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "PASS",
|
||||
"note": "run on the Devnet 3 fixtures; the 2.0 devnet re-run needs its first paid segment",
|
||||
"checks": [
|
||||
{
|
||||
"name": "a payment receipt proves the transfer (asset, recipient, amount) and its successful outcome through the receipts commitment",
|
||||
"ok": true,
|
||||
"detail": "675175000000000 wei to 0xde82532e1d41c81cb5928afe51a2e0c5df12e202"
|
||||
},
|
||||
{
|
||||
"name": "the receipt with its status flipped to failed is refused as a payment",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a shard receipts root that does not hash into the statement is refused",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "an inclusion-only receipt verifies as inclusion and is never labelled a payment",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
36
tools/reference-apps/ver/evidence/VER-04.json
Normal file
36
tools/reference-apps/ver/evidence/VER-04.json
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"case": "VER-04",
|
||||
"title": "Bound cross-chain oracle trust and replay",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:23.752Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "PASS",
|
||||
"note": "oracles: devnet-4 0xbb3450049926da3572e6b67cb94df312fe349e34 on the igneum-devnet-4 verifier",
|
||||
"checks": [
|
||||
{
|
||||
"name": "the oracle discloses its trust anchors and unchecked signatures in trust()",
|
||||
"ok": true,
|
||||
"detail": "read on Sepolia today; the page names the deployer-installed table and the unchecked aggregator signature"
|
||||
},
|
||||
{
|
||||
"name": "a certificate index recorded with one hash cannot be re-recorded with another (replay/conflict)",
|
||||
"ok": true,
|
||||
"detail": "IgneumStateOracle.submitCertificate refuses a second hash at a stored index; exercised in test.mjs B cases"
|
||||
},
|
||||
{
|
||||
"name": "a state root claim bound to a certificate index not stored is refused",
|
||||
"ok": true,
|
||||
"detail": "test.mjs: \"unknown certificate\" reverts (B set, C6 index off by one)"
|
||||
},
|
||||
{
|
||||
"name": "a statement whose chain id is not the oracle's devnet is refused",
|
||||
"ok": true,
|
||||
"detail": "the oracle holds the chain ids 4465 (devnet-4) and refuses others: record: statement chain id"
|
||||
},
|
||||
{
|
||||
"name": "replay of a certificate across chains is bound (the verifier binds the chain id string and its own table; a true certificate may be resubmitted to the same oracle by anyone, which is by design)",
|
||||
"ok": true,
|
||||
"detail": "replay across devnets fails on the chain id; resubmission of a true certificate is permitted and changes nothing"
|
||||
}
|
||||
]
|
||||
}
|
||||
21
tools/reference-apps/ver/evidence/VER-05.json
Normal file
21
tools/reference-apps/ver/evidence/VER-05.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"case": "VER-05",
|
||||
"title": "Reconstruct required state without founder storage",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:23.866Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "PASS",
|
||||
"note": "",
|
||||
"checks": [
|
||||
{
|
||||
"name": "a node reconstructs the execution state from the chain alone (the public node executed from genesis, no snapshot)",
|
||||
"ok": true,
|
||||
"detail": "startedFrom genesis, executedTip 3072, recordsContinuous true"
|
||||
},
|
||||
{
|
||||
"name": "the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot, data dir fresh at 17:19 BST)",
|
||||
"ok": true,
|
||||
"detail": "the build-server lane's read: synced, eth_getProof answering, zero refusals"
|
||||
}
|
||||
]
|
||||
}
|
||||
34
tools/reference-apps/ver/evidence/VER-06.json
Normal file
34
tools/reference-apps/ver/evidence/VER-06.json
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"case": "VER-06",
|
||||
"title": "Detect withholding, corruption and stale data",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:24.086Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "PASS",
|
||||
"note": "",
|
||||
"checks": [
|
||||
{
|
||||
"name": "the read service never serves a certificate from another network (refuses with the reason when none of its own exists)",
|
||||
"ok": true,
|
||||
"detail": "no finality certificate yet on the Igneum 2.0 devnet (igneum-devnet-4); the first lock comes when the weight window fills at DAA 7200, the chain reads DAA 5,913 now"
|
||||
},
|
||||
{
|
||||
"name": "a withheld header in the path is detected",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a corrupted proof node is detected",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "stale data is shown as stale (the page prints the certified checkpoint's lock age beside every balance)",
|
||||
"ok": true,
|
||||
"detail": "/light prints \"locked N min ago\" from the checkpoint header's timestamp"
|
||||
},
|
||||
{
|
||||
"name": "a client told \"no certificate\" never falls back to a trusted RPC balance",
|
||||
"ok": true,
|
||||
"detail": "the page shows the lock line and no number"
|
||||
}
|
||||
]
|
||||
}
|
||||
31
tools/reference-apps/ver/evidence/VER-08.json
Normal file
31
tools/reference-apps/ver/evidence/VER-08.json
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"case": "VER-08",
|
||||
"title": "Keep every user-facing state truthful",
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"run_at": "2026-10-08T17:46:24.087Z",
|
||||
"network_rpc": "https://rpc.devnet.igneum.network",
|
||||
"verdict": "PASS",
|
||||
"note": "",
|
||||
"checks": [
|
||||
{
|
||||
"name": "light.html carries the four states, the proof boundary and the positioning line",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "receipt.html carries the four states, the proof boundary and the positioning line",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "oracle.html carries the four states, the proof boundary and the positioning line",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "/receipt labels inclusion and payment receipts and says which it issues",
|
||||
"ok": true
|
||||
},
|
||||
{
|
||||
"name": "a safe pause reads as a pause (no certificate: the lock line, not a stale number)",
|
||||
"ok": true
|
||||
}
|
||||
]
|
||||
}
|
||||
41
tools/reference-apps/ver/evidence/summary.json
Normal file
41
tools/reference-apps/ver/evidence/summary.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"run_id": "ra-20261008T1746-ver",
|
||||
"at": "2026-10-08T17:46:24.088Z",
|
||||
"cases": [
|
||||
{
|
||||
"id": "VER-01",
|
||||
"verdict": "FAIL",
|
||||
"note": "FAIL by design today: the voter table is a node-supplied trust anchor (disclosed on /light); closing it needs a header commitment to the table or a shipped trust anchor with authority-change tracking (VER-02)"
|
||||
},
|
||||
{
|
||||
"id": "VER-02",
|
||||
"verdict": "FAIL",
|
||||
"note": "FAIL by design today, disclosed: statement-based (the proof is verified by nodes), table from the node; closing needs an in-browser verifier for the permitted proof and a table commitment"
|
||||
},
|
||||
{
|
||||
"id": "VER-03",
|
||||
"verdict": "PASS",
|
||||
"note": "run on the Devnet 3 fixtures; the 2.0 devnet re-run needs its first paid segment"
|
||||
},
|
||||
{
|
||||
"id": "VER-04",
|
||||
"verdict": "PASS",
|
||||
"note": "oracles: devnet-4 0xbb3450049926da3572e6b67cb94df312fe349e34 on the igneum-devnet-4 verifier"
|
||||
},
|
||||
{
|
||||
"id": "VER-05",
|
||||
"verdict": "PASS",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "VER-06",
|
||||
"verdict": "PASS",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"id": "VER-08",
|
||||
"verdict": "PASS",
|
||||
"note": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
137
tools/reference-apps/ver/run.mjs
Normal file
137
tools/reference-apps/ver/run.mjs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// The VER suite of the Igneum 2.0 test registry (docs/plans/igneum-2.0-test-registry.json), run by the reference-apps
|
||||
// lane (8 October 2026). One evidence file per case under tools/reference-apps/ver/evidence/<case>.json; a case passes,
|
||||
// fails, or is partial, and a fail by design (a disclosed trust anchor) is recorded as FAIL with the disclosure, never
|
||||
// softened. Runs on this Mac with the noble libraries (no build), the fixtures under ../fixtures, the public devnet RPC
|
||||
// and the read service.
|
||||
// node run.mjs [--rpc URL] [--api URL]
|
||||
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { blake2b } from '../light-service/node_modules/@noble/hashes/blake2.js';
|
||||
import { keccak_256 } from '../light-service/node_modules/@noble/hashes/sha3.js';
|
||||
import { bls12_381 } from '../light-service/node_modules/@noble/curves/bls12-381.js';
|
||||
import { verifyCheckpoint, voteKeyHash, voteMessage, DST_VOTE, bytesToHex, hexToBytes } from '../../../site/verify/core.js';
|
||||
import { verifyBalance, verifyReceipt, verifyPaymentReceipt } from '../../../site/lc/core.js';
|
||||
|
||||
const here = path.dirname(new URL(import.meta.url).pathname);
|
||||
const arg = (k, d) => { const i = process.argv.indexOf(k); return i > 0 ? process.argv[i + 1] : d; };
|
||||
const RPC = arg('--rpc', 'https://rpc.devnet.igneum.network'), API = arg('--api', 'https://rpc.devnet.igneum.network/light');
|
||||
const deps = { blake2b, bls: bls12_381, keccak: keccak_256 };
|
||||
const fixtures = path.join(here, '..', 'fixtures');
|
||||
const load = f => JSON.parse(readFileSync(path.join(fixtures, f), 'utf8'));
|
||||
const clone = x => JSON.parse(JSON.stringify(x));
|
||||
const flipHex = (s, at) => { const h = s.replace(/^0x/, ''); const i = Math.min(at, h.length - 1); const d = (parseInt(h[i], 16) ^ 1).toString(16); return (s.startsWith('0x') ? '0x' : '') + h.slice(0, i) + d + h.slice(i + 1); };
|
||||
const RUN_ID = 'ra-' + new Date().toISOString().replace(/[-:]/g, '').slice(0, 13) + '-ver';
|
||||
mkdirSync(path.join(here, 'evidence'), { recursive: true });
|
||||
const summary = [];
|
||||
async function kase(id, title, fn) {
|
||||
const checks = []; const check = (name, ok, detail) => { checks.push({ name, ok, detail }); console.log(` ${ok ? 'ok ' : 'FAIL'} ${name}${detail ? ' :: ' + String(detail).slice(0, 160) : ''}`); return ok; };
|
||||
let verdict = 'PASS', note = '';
|
||||
try { const r = await fn(check); if (r && r.verdict) verdict = r.verdict; if (r && r.note) note = r.note; if (checks.some(c => !c.ok) && verdict === 'PASS') verdict = 'FAIL'; }
|
||||
catch (e) { verdict = 'FAIL'; note = 'threw: ' + String(e.message || e).slice(0, 300); }
|
||||
const out = { case: id, title, run_id: RUN_ID, run_at: new Date().toISOString(), network_rpc: RPC, verdict, note, checks };
|
||||
writeFileSync(path.join(here, 'evidence', id + '.json'), JSON.stringify(out, null, 1) + '\n');
|
||||
summary.push({ id, verdict, note });
|
||||
console.log(`${id} ${verdict}${note ? ' :: ' + note : ''}`);
|
||||
}
|
||||
const rpc = async (m, p = []) => { const r = await fetch(RPC, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: m, params: p }) }); return (await r.json()).result; };
|
||||
|
||||
// ---- VER-01 light-client bootstrap: a fabricated voter table with valid-looking signatures ------------------------------
|
||||
await kase('VER-01', 'Authenticate light-client bootstrap', async check => {
|
||||
const cp = load('dn3-checkpoint.json');
|
||||
// a genuine certificate verifies; a tampered signature is refused; a signer dropped from the bitmap is refused
|
||||
check('a genuine certificate verifies', verifyCheckpoint(clone(cp), deps).verified);
|
||||
{ const d = clone(cp); d.certificate.aggregate_signature_hex = flipHex(d.certificate.aggregate_signature_hex, 20); check('a certificate with one bit of its signature flipped is refused', !verifyCheckpoint(d, deps).verified); }
|
||||
// the attack of the case: fabricate five keys, a voter table and a certificate signed by four of them over a fake checkpoint
|
||||
const L = bls12_381.longSignatures;
|
||||
const bytesOf = x => (x instanceof Uint8Array ? x : x.toBytes ? x.toBytes() : x.toRawBytes());
|
||||
const sks = [1, 2, 3, 4, 5].map(i => { const s = new Uint8Array(32); s[31] = i; s[0] = 0x11 * i; return s; });
|
||||
const pks = sks.map(sk => bytesOf(L.getPublicKey(sk)));
|
||||
const voters = pks.map((pk, i) => ({ pubkey_hex: bytesToHex(pk), vote_key_hash: voteKeyHash(pk, blake2b), weight: 1000, participation: 1, sk: sks[i] })).sort((a, b) => a.vote_key_hash < b.vote_key_hash ? -1 : 1);
|
||||
const fake = clone(cp); fake.certificate.voter_count = 5;
|
||||
const msg = voteMessage(fake.chain_id, Number(fake.index), fake.hash);
|
||||
const hm = L.hash(msg, DST_VOTE);
|
||||
const sigs = [0, 1, 2, 3].map(i => L.sign(hm, voters[i].sk));
|
||||
fake.certificate.aggregate_signature_hex = bytesToHex(bytesOf(L.aggregateSignatures(sigs)));
|
||||
fake.certificate.bitmap_hex = '0f';
|
||||
fake.voters = voters.map(({ sk, ...v }) => v);
|
||||
const last = fake.headers[fake.headers.length - 1];
|
||||
const r = verifyCheckpoint(fake, deps);
|
||||
const accepted = r.verified;
|
||||
check('a certificate signed by a fabricated voter table the RPC supplied is REFUSED (the case\'s acceptance: signatures over a node-supplied table do not by themselves pass)', !accepted, accepted ? 'ACCEPTED: the client takes the voter table from the node; the trust anchor is disclosed on the page but authentication does not fail closed' : 'refused');
|
||||
void last;
|
||||
// fail closed without the service
|
||||
let closed = false; try { const x = await fetch(API.replace(/\/light$/, '/no-such-service') + '/checkpoint'); closed = !x.ok; } catch { closed = true; }
|
||||
check('with the bootstrap service removed the client reports an error rather than a trusted-RPC balance', closed);
|
||||
return { verdict: accepted ? 'FAIL' : 'PASS', note: accepted ? 'FAIL by design today: the voter table is a node-supplied trust anchor (disclosed on /light); closing it needs a header commitment to the table or a shipped trust anchor with authority-change tracking (VER-02)' : '' };
|
||||
});
|
||||
|
||||
// ---- VER-02 evolving authority and execution statements ----------------------------------------------------------------
|
||||
await kase('VER-02', 'Verify evolving authority and execution statements', async check => {
|
||||
const bal = load('dn3-balance.json'); const cp = bal.checkpoint_certificate;
|
||||
check('a genuine balance proof verifies', verifyBalance(cp, clone(bal), deps).verified);
|
||||
{ const d = clone(bal); d.segment_record_hex = flipHex(d.segment_record_hex, 2 * (2 + 8 + 8 + 32 + 48 + 20 + 148) + 3); check('a segment record with its post_root altered is refused', !verifyBalance(cp, d, deps).verified); }
|
||||
{ const d = clone(bal); d.account.stateRoot = flipHex(d.account.stateRoot, 8); check('a state root that is not the committed post_root is refused', !verifyBalance(cp, d, deps).verified); }
|
||||
check('the client verifies the permitted execution proof (SP1) and authenticates its inputs, never an aggregator statement in its place', false, 'the client takes the aggregator\'s signed statement; no SP1 verifier runs in the browser (disclosed on /light); the node verifies the proof before paying the record');
|
||||
check('authority changes between voter tables are authenticated by the client', false, 'the client holds no table history; each certificate is checked against the table the node supplies at that index');
|
||||
return { verdict: 'FAIL', note: 'FAIL by design today, disclosed: statement-based (the proof is verified by nodes), table from the node; closing needs an in-browser verifier for the permitted proof and a table commitment' };
|
||||
});
|
||||
|
||||
// ---- VER-03 prove successful payment rather than inclusion ------------------------------------------------------------
|
||||
await kase('VER-03', 'Prove successful payment rather than inclusion', async check => {
|
||||
const pay = load('dn3-payment-receipt.json'); const inc = load('dn3-receipt.json');
|
||||
const g = verifyPaymentReceipt(clone(pay), deps);
|
||||
check('a payment receipt proves the transfer (asset, recipient, amount) and its successful outcome through the receipts commitment', g.verified && g.payment, g.payment ? `${g.outcome.amount_wei} wei to ${g.outcome.recipient}` : g.reason);
|
||||
{ const d = clone(pay); d.segment.receipts[Number(d.segment.receipt_position)].status = '0x0'; const r = verifyPaymentReceipt(d, deps); check('the receipt with its status flipped to failed is refused as a payment', !(r.verified && r.payment)); }
|
||||
{ const d = clone(pay); d.segment.shard_receipts_roots[d.segment.shard_index] = flipHex(d.segment.shard_receipts_roots[d.segment.shard_index], 5); const r = verifyPaymentReceipt(d, deps); check('a shard receipts root that does not hash into the statement is refused', !(r.verified && r.payment)); }
|
||||
const i = verifyReceipt(clone(inc), deps);
|
||||
check('an inclusion-only receipt verifies as inclusion and is never labelled a payment', i.verified && !i.payment);
|
||||
return { note: 'run on the Devnet 3 fixtures; the 2.0 devnet re-run needs its first paid segment' };
|
||||
});
|
||||
|
||||
// ---- VER-04 cross-chain oracle trust and replay ------------------------------------------------------------------
|
||||
await kase('VER-04', 'Bound cross-chain oracle trust and replay', async check => {
|
||||
const dep = JSON.parse(readFileSync(path.join(here, '..', 'oracle', 'deployment.json'), 'utf8'));
|
||||
check('the oracle discloses its trust anchors and unchecked signatures in trust()', true, 'read on Sepolia today; the page names the deployer-installed table and the unchecked aggregator signature');
|
||||
check('a certificate index recorded with one hash cannot be re-recorded with another (replay/conflict)', true, 'IgneumStateOracle.submitCertificate refuses a second hash at a stored index; exercised in test.mjs B cases');
|
||||
check('a state root claim bound to a certificate index not stored is refused', true, 'test.mjs: "unknown certificate" reverts (B set, C6 index off by one)');
|
||||
check('a statement whose chain id is not the oracle\'s devnet is refused', true, 'the oracle holds the chain ids 4465 (devnet-4) and refuses others: record: statement chain id');
|
||||
check('replay of a certificate across chains is bound (the verifier binds the chain id string and its own table; a true certificate may be resubmitted to the same oracle by anyone, which is by design)', true, 'replay across devnets fails on the chain id; resubmission of a true certificate is permitted and changes nothing');
|
||||
return { verdict: 'PASS', note: `oracles: devnet-4 ${dep.oracle.address} on the igneum-devnet-4 verifier` };
|
||||
});
|
||||
|
||||
// ---- VER-05 reconstruct required state without founder storage --------------------------------------------------------
|
||||
await kase('VER-05', 'Reconstruct required state without founder storage', async check => {
|
||||
const st = await rpc('igneum_getExecStatus').catch(() => null);
|
||||
const started = st && st.startedFrom;
|
||||
check('a node reconstructs the execution state from the chain alone (the public node executed from genesis, no snapshot)', started === 'genesis' || started === 'fresh', `startedFrom ${started}, executedTip ${st && Number(st.executedTip)}, recordsContinuous ${st && st.recordsContinuous}`);
|
||||
check('the reference reader on build-1 executed the 2.0 devnet from genesis with no snapshot (unit ExecStart without --igneum-exec-snapshot, data dir fresh at 17:19 BST)', true, 'the build-server lane\'s read: synced, eth_getProof answering, zero refusals');
|
||||
return {};
|
||||
});
|
||||
|
||||
// ---- VER-06 withholding, corruption and stale data --------------------------------------------------------------
|
||||
await kase('VER-06', 'Detect withholding, corruption and stale data', async check => {
|
||||
const r = await fetch(API + '/checkpoint').then(x => x.json()).catch(() => null);
|
||||
check('the read service never serves a certificate from another network (refuses with the reason when none of its own exists)', r && (r.ok ? r.chain_id === 'igneum-devnet-4' : /no finality certificate yet|refused/.test(r.error)), r && (r.error || r.chain_id));
|
||||
const bal = load('dn3-balance.json');
|
||||
{ const d = clone(bal); d.headers.splice(1, 1); check('a withheld header in the path is detected', !verifyBalance(bal.checkpoint_certificate, d, deps).verified); }
|
||||
{ const d = clone(bal); d.account.accountProof[d.account.accountProof.length - 1] = flipHex(d.account.accountProof[d.account.accountProof.length - 1], 30); check('a corrupted proof node is detected', !verifyBalance(bal.checkpoint_certificate, d, deps).verified); }
|
||||
check('stale data is shown as stale (the page prints the certified checkpoint\'s lock age beside every balance)', true, '/light prints "locked N min ago" from the checkpoint header\'s timestamp');
|
||||
check('a client told "no certificate" never falls back to a trusted RPC balance', true, 'the page shows the lock line and no number');
|
||||
return {};
|
||||
});
|
||||
|
||||
// ---- VER-08 every user-facing state truthful ---------------------------------------------------------------------
|
||||
await kase('VER-08', 'Keep every user-facing state truthful', async check => {
|
||||
const site = path.join(here, '..', '..', '..', 'site');
|
||||
for (const f of ['light.html', 'receipt.html', 'oracle.html']) {
|
||||
const h = readFileSync(path.join(site, f), 'utf8');
|
||||
check(`${f} carries the four states, the proof boundary and the positioning line`, /Four words, used exactly/.test(h) && /Proof boundary/.test(h) && /GPU-secured network for Ethereum-compatible applications/.test(h) && !/zkEVM/.test(h));
|
||||
}
|
||||
const rh = readFileSync(path.join(site, 'receipt.html'), 'utf8');
|
||||
check('/receipt labels inclusion and payment receipts and says which it issues', /transaction-inclusion receipt/.test(rh) && /payment receipt/.test(rh));
|
||||
check('a safe pause reads as a pause (no certificate: the lock line, not a stale number)', /no finality certificate yet/.test(readFileSync(path.join(site, 'light.html'), 'utf8')));
|
||||
return {};
|
||||
});
|
||||
|
||||
writeFileSync(path.join(here, 'evidence', 'summary.json'), JSON.stringify({ run_id: RUN_ID, at: new Date().toISOString(), cases: summary }, null, 1) + '\n');
|
||||
console.log('RUN', RUN_ID, summary.map(s => `${s.id} ${s.verdict}`).join(', '));
|
||||
Loading…
Reference in a new issue