|
| 1 | +/** |
| 2 | + * Comprehensive demo of all trace output features. |
| 3 | + * |
| 4 | + * Run with dedup (normal): |
| 5 | + * pnpm hardhat run scripts/demo-trace-output.ts -vvvv |
| 6 | + * |
| 7 | + * Run without dedup (all traces): |
| 8 | + * pnpm hardhat run scripts/demo-trace-output.ts -vvvvv |
| 9 | + * |
| 10 | + * For ANSI color output (red headers on failure, dim on success): |
| 11 | + * FORCE_COLOR=3 pnpm hardhat run scripts/demo-trace-output.ts -vvvv |
| 12 | + * |
| 13 | + * What to look for: |
| 14 | + * - All call kinds: CALL, CREATE, STATICCALL, DELEGATECALL |
| 15 | + * - Connection labels: "Trace from connection #0 (default)", "#1 (node)" |
| 16 | + * - -vvvv: dedup active — single counter.write.inc() → 1 trace (not 3) |
| 17 | + * - -vvvvv: no dedup — estimateGas + sendTx both shown per write |
| 18 | + * - Red header on failed RPC, dim on success |
| 19 | + * - Batch-mined txs grouped under 1 "Traces from" header |
| 20 | + */ |
| 21 | +import hre from "hardhat"; |
| 22 | + |
| 23 | +// ═══════════════════════════════════════════════════════════════════════ |
| 24 | +// Setup: two connections to two independent EDR-simulated networks |
| 25 | +// ═══════════════════════════════════════════════════════════════════════ |
| 26 | +const connA = await hre.network.connect("default"); |
| 27 | +const connB = await hre.network.connect("node"); |
| 28 | + |
| 29 | +const [counterA, counterB, revertContract] = await Promise.all([ |
| 30 | + connA.viem.deployContract("Counter", []), |
| 31 | + connB.viem.deployContract("Counter", []), |
| 32 | + connA.viem.deployContract("Revert", []), |
| 33 | +]); |
| 34 | + |
| 35 | +const logic = await connA.viem.deployContract("Logic", []); |
| 36 | +const orchestrator = await connA.viem.deployContract("Orchestrator", [ |
| 37 | + logic.address, |
| 38 | +]); |
| 39 | +const factory = await connA.viem.deployContract("CallTypesFactory", []); |
| 40 | + |
| 41 | +// ═══════════════════════════════════════════════════════════════════════ |
| 42 | +// 1. All call kinds — CALL, CREATE, STATICCALL, DELEGATECALL, events |
| 43 | +// ═══════════════════════════════════════════════════════════════════════ |
| 44 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 45 | +console.log("║ 1 — All call kinds ║"); |
| 46 | +console.log("╚══════════════════════════════════════════════════════╝\n"); |
| 47 | + |
| 48 | +// CALL + event: Orchestrator → Logic.setValue (external CALL, emits ValueSet) |
| 49 | +console.log(" 1a. CALL + event: Orchestrator.doCall(42)\n"); |
| 50 | +await orchestrator.write.doCall([42n]); |
| 51 | + |
| 52 | +// STATICCALL: Orchestrator → Logic.getValue (view → STATICCALL) |
| 53 | +console.log("\n 1b. STATICCALL: Orchestrator.doStaticCall()\n"); |
| 54 | +await orchestrator.read.doStaticCall(); |
| 55 | + |
| 56 | +// CREATE: deploy new contracts inline (via CallTypesFactory) |
| 57 | +console.log("\n 1c. CREATE: CallTypesFactory.deploy()\n"); |
| 58 | +await factory.write.deploy(); |
| 59 | + |
| 60 | +// Mixed: DELEGATECALL + CALL + STATICCALL + event in one transaction |
| 61 | +console.log( |
| 62 | + "\n 1d. Mixed (all types in 1 tx): Orchestrator.doAllCallTypes(7, 3)\n", |
| 63 | +); |
| 64 | +await orchestrator.write.doAllCallTypes([7n, 3n]); |
| 65 | + |
| 66 | +// DELEGATECALL: Orchestrator → Logic.setValue via delegatecall |
| 67 | +// NOTE: This must come last — delegatecall overwrites Orchestrator's storage |
| 68 | +// (slot 0 = logic address), making subsequent calls to logic.* fail. |
| 69 | +console.log("\n 1e. DELEGATECALL: Orchestrator.doDelegateCall(42)\n"); |
| 70 | +await orchestrator.write.doDelegateCall([42n]); |
| 71 | + |
| 72 | +// ═══════════════════════════════════════════════════════════════════════ |
| 73 | +// 2. Multi-connection — sequential txs on two networks |
| 74 | +// Headers should show different connection labels / colors |
| 75 | +// ═══════════════════════════════════════════════════════════════════════ |
| 76 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 77 | +console.log("║ 2 — Multi-connection, sequential ║"); |
| 78 | +console.log("╚══════════════════════════════════════════════════════╝\n"); |
| 79 | + |
| 80 | +await counterA.write.inc(); |
| 81 | +await counterB.write.inc(); |
| 82 | + |
| 83 | +// ═══════════════════════════════════════════════════════════════════════ |
| 84 | +// 3. Multi-connection — concurrent txs on both networks |
| 85 | +// Traces from each connection are atomic (no interleaving) |
| 86 | +// ═══════════════════════════════════════════════════════════════════════ |
| 87 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 88 | +console.log("║ 3 — Multi-connection, concurrent (3 txs each) ║"); |
| 89 | +console.log("╚══════════════════════════════════════════════════════╝\n"); |
| 90 | + |
| 91 | +await Promise.all([ |
| 92 | + counterA.write.inc(), |
| 93 | + counterA.write.inc(), |
| 94 | + counterA.write.inc(), |
| 95 | + counterB.write.inc(), |
| 96 | + counterB.write.inc(), |
| 97 | + counterB.write.inc(), |
| 98 | +]); |
| 99 | + |
| 100 | +// ═══════════════════════════════════════════════════════════════════════ |
| 101 | +// 4. Batch mining with evm_mine — grouped traces under a single header |
| 102 | +// "Traces from connection #N (network): evm_mine" (plural) |
| 103 | +// ═══════════════════════════════════════════════════════════════════════ |
| 104 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 105 | +console.log("║ 4 — evm_mine: 5 txs in one block (grouping demo) ║"); |
| 106 | +console.log("╚══════════════════════════════════════════════════════╝\n"); |
| 107 | +console.log(' → expect 1 "Traces from" header + 5 traces below it\n'); |
| 108 | + |
| 109 | +await connA.provider.request({ method: "evm_setAutomine", params: [false] }); |
| 110 | +await Promise.all(Array.from({ length: 5 }, () => counterA.write.inc())); |
| 111 | +await connA.provider.request({ method: "evm_mine", params: [] }); |
| 112 | +await connA.provider.request({ method: "evm_setAutomine", params: [true] }); |
| 113 | + |
| 114 | +// ═══════════════════════════════════════════════════════════════════════ |
| 115 | +// 5. Failed RPC calls — header should be red, method name highlighted |
| 116 | +// ═══════════════════════════════════════════════════════════════════════ |
| 117 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 118 | +console.log("║ 5 — Failing calls (red header / method name) ║"); |
| 119 | +console.log("╚══════════════════════════════════════════════════════╝\n"); |
| 120 | + |
| 121 | +// 5a. Revert contract with known error message |
| 122 | +console.log(" 5a. Revert.boom() — reverts with 'Boom':\n"); |
| 123 | +try { |
| 124 | + await revertContract.read.boom(); |
| 125 | +} catch (e: any) { |
| 126 | + console.log(` → Reverted: ${e.details ?? e.message}\n`); |
| 127 | +} |
| 128 | + |
| 129 | +// 5b. Invalid selector on Counter (no fallback) |
| 130 | +console.log(" 5b. Invalid selector 0xdeadbeef on Counter:\n"); |
| 131 | +try { |
| 132 | + await connA.provider.request({ |
| 133 | + method: "eth_call", |
| 134 | + params: [{ to: counterA.address, data: "0xdeadbeef" }, "latest"], |
| 135 | + }); |
| 136 | +} catch { |
| 137 | + console.log(" → Reverted as expected\n"); |
| 138 | +} |
| 139 | + |
| 140 | +// 5c. Orchestrator.doCallThatReverts(0) — nested revert |
| 141 | +// Deploy fresh orchestrator since the earlier doDelegateCall corrupted storage |
| 142 | +console.log(" 5c. Orchestrator.doCallThatReverts(0) — nested revert:\n"); |
| 143 | +const logic2 = await connA.viem.deployContract("Logic", []); |
| 144 | +const orch2 = await connA.viem.deployContract("Orchestrator", [logic2.address]); |
| 145 | +try { |
| 146 | + await orch2.write.doCallThatReverts([0n]); |
| 147 | +} catch (e: any) { |
| 148 | + console.log(` → Reverted: ${e.details ?? e.message}\n`); |
| 149 | +} |
| 150 | + |
| 151 | +// ═══════════════════════════════════════════════════════════════════════ |
| 152 | +// 6. Deduplication — single write triggers estimateGas + sendTx + receipt |
| 153 | +// but only ONE trace should appear |
| 154 | +// ═══════════════════════════════════════════════════════════════════════ |
| 155 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 156 | +console.log("║ 6 — Deduplication ║"); |
| 157 | +console.log("╚══════════════════════════════════════════════════════╝\n"); |
| 158 | +console.log(" A single counter.write.inc() triggers 3 RPC calls:"); |
| 159 | +console.log(" 1. eth_estimateGas → suppressed at -vvvv, shown at -vvvvv"); |
| 160 | +console.log(" 2. eth_sendTransaction → always shown (this is the real tx)"); |
| 161 | +console.log(" 3. eth_getTransactionReceipt → no traces (read-only)"); |
| 162 | +console.log( |
| 163 | + " At -vvvv: 1 trace. At -vvvvv: 2 traces (estimateGas + sendTx).\n", |
| 164 | +); |
| 165 | + |
| 166 | +await counterA.write.inc(); |
| 167 | + |
| 168 | +// ═══════════════════════════════════════════════════════════════════════ |
| 169 | +// Summary |
| 170 | +// ═══════════════════════════════════════════════════════════════════════ |
| 171 | +console.log("\n╔══════════════════════════════════════════════════════╗"); |
| 172 | +console.log("║ Summary ║"); |
| 173 | +console.log("╚══════════════════════════════════════════════════════╝"); |
| 174 | +console.log(""); |
| 175 | +console.log(" -vvvv: dedup + suppression active (1 trace per write)"); |
| 176 | +console.log(" -vvvvv: no dedup (estimateGas + sendTx both shown)"); |
| 177 | +console.log(""); |
| 178 | +console.log("Done."); |
0 commit comments