Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions v-next/example-project/contracts/CallTypes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// SPDX-License-Identifier: MIT
Copy link
Copy Markdown
Contributor Author

@ChristopherDedominici ChristopherDedominici Mar 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IGNORE: only used for manual testing - will be removed before merging.

Run pnpm hardhat run scripts/demo-trace-output.ts -vvvv (or -vvvvv) in example-project to see a demo

Copy link
Copy Markdown
Contributor Author

@ChristopherDedominici ChristopherDedominici Mar 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I will keep them, it could be useful for the future

pragma solidity ^0.8.0;

import "hardhat/console.sol";

/// @notice Demonstrates different EVM call types for trace output testing.
/// Used by scripts/demo-call-types.ts to exercise [CALL], [CREATE],
/// [STATICCALL], [DELEGATECALL], and [EVENT] trace tags.

/// @notice Helper contract with view and write functions
contract Logic {
event ValueSet(address indexed setter, uint256 value);

uint256 public value;

function setValue(uint256 _value) external {
console.log("Logic.setValue:", _value);
value = _value;
emit ValueSet(msg.sender, _value);
}

function getValue() external view returns (uint256) {
return value;
}

function pureAdd(uint256 a, uint256 b) external pure returns (uint256) {
return a + b;
}

function mustBePositive(uint256 _value) external {
require(_value > 0, "Value must be positive");
console.log("Logic.mustBePositive:", _value);
value = _value;
emit ValueSet(msg.sender, _value);
}
}

/// @notice Orchestrator that calls Logic, exercising multiple call types
contract Orchestrator {
event Orchestrated(uint256 result);

Logic public logic;

constructor(Logic _logic) {
logic = _logic;
}

/// @notice Regular external CALL to Logic.setValue
function doCall(uint256 _value) external {
logic.setValue(_value);
}

/// @notice Calls Logic.mustBePositive — reverts when _value == 0
function doCallThatReverts(uint256 _value) external {
logic.mustBePositive(_value);
}

/// @notice External view call to Logic → produces STATICCALL
function doStaticCall() external view returns (uint256) {
return logic.getValue();
}

/// @notice Explicit delegatecall to Logic.setValue → produces DELEGATECALL trace
function doDelegateCall(uint256 _value) external returns (bool success) {
bytes memory data = abi.encodeWithSelector(Logic.setValue.selector, _value);
(success, ) = address(logic).delegatecall(data);
}

/// @notice Mixed: DELEGATECALL + CALL (Logic.setValue) + STATICCALL (Logic.getValue)
function doAllCallTypes(uint256 a, uint256 b) external returns (uint256) {
// DELEGATECALL to Logic.pureAdd (uses pureAdd to avoid storage collision)
bytes memory data = abi.encodeWithSelector(Logic.pureAdd.selector, a, b);
address(logic).delegatecall(data);
// CALL to Logic.setValue
logic.setValue(a + b);
// STATICCALL to Logic.getValue
uint256 readBack = logic.getValue();
emit Orchestrated(readBack);
return readBack;
}
}

/// @notice Factory that creates contracts in a single transaction
contract CallTypesFactory {
event ContractsDeployed(address indexed logic, address indexed orchestrator);

function deploy() external returns (address, address) {
Logic logicContract = new Logic();
Orchestrator orch = new Orchestrator(logicContract);
emit ContractsDeployed(address(logicContract), address(orch));
return (address(logicContract), address(orch));
}
}
178 changes: 178 additions & 0 deletions v-next/example-project/scripts/demo-trace-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IGNORE: only used for manual testing - will be removed before merging.

Run pnpm hardhat run scripts/demo-trace-output.ts -vvvv (or -vvvvv) in example-project to see a demo

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I will keep them, it could be useful for the future

* Comprehensive demo of all trace output features.
*
* Run with dedup (normal):
* pnpm hardhat run scripts/demo-trace-output.ts -vvvv
*
* Run without dedup (all traces):
* pnpm hardhat run scripts/demo-trace-output.ts -vvvvv
*
* For ANSI color output (red headers on failure, dim on success):
* FORCE_COLOR=3 pnpm hardhat run scripts/demo-trace-output.ts -vvvv
*
* What to look for:
* - All call kinds: CALL, CREATE, STATICCALL, DELEGATECALL
* - Connection labels: "Trace from connection #0 (default)", "#1 (node)"
* - -vvvv: dedup active — single counter.write.inc() → 1 trace (not 3)
* - -vvvvv: no dedup — estimateGas + sendTx both shown per write
* - Red header on failed RPC, dim on success
* - Batch-mined txs grouped under 1 "Traces from" header
*/
import hre from "hardhat";

// ═══════════════════════════════════════════════════════════════════════
// Setup: two connections to two independent EDR-simulated networks
// ═══════════════════════════════════════════════════════════════════════
const connA = await hre.network.connect("default");
const connB = await hre.network.connect("node");

const [counterA, counterB, revertContract] = await Promise.all([
connA.viem.deployContract("Counter", []),
connB.viem.deployContract("Counter", []),
connA.viem.deployContract("Revert", []),
]);

const logic = await connA.viem.deployContract("Logic", []);
const orchestrator = await connA.viem.deployContract("Orchestrator", [
logic.address,
]);
const factory = await connA.viem.deployContract("CallTypesFactory", []);

// ═══════════════════════════════════════════════════════════════════════
// 1. All call kinds — CALL, CREATE, STATICCALL, DELEGATECALL, events
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ 1 — All call kinds ║");
console.log("╚══════════════════════════════════════════════════════╝\n");

// CALL + event: Orchestrator → Logic.setValue (external CALL, emits ValueSet)
console.log(" 1a. CALL + event: Orchestrator.doCall(42)\n");
await orchestrator.write.doCall([42n]);

// STATICCALL: Orchestrator → Logic.getValue (view → STATICCALL)
console.log("\n 1b. STATICCALL: Orchestrator.doStaticCall()\n");
await orchestrator.read.doStaticCall();

// CREATE: deploy new contracts inline (via CallTypesFactory)
console.log("\n 1c. CREATE: CallTypesFactory.deploy()\n");
await factory.write.deploy();

// Mixed: DELEGATECALL + CALL + STATICCALL + event in one transaction
console.log(
"\n 1d. Mixed (all types in 1 tx): Orchestrator.doAllCallTypes(7, 3)\n",
);
await orchestrator.write.doAllCallTypes([7n, 3n]);

// DELEGATECALL: Orchestrator → Logic.setValue via delegatecall
// NOTE: This must come last — delegatecall overwrites Orchestrator's storage
// (slot 0 = logic address), making subsequent calls to logic.* fail.
console.log("\n 1e. DELEGATECALL: Orchestrator.doDelegateCall(42)\n");
await orchestrator.write.doDelegateCall([42n]);

// ═══════════════════════════════════════════════════════════════════════
// 2. Multi-connection — sequential txs on two networks
// Headers should show different connection labels / colors
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ 2 — Multi-connection, sequential ║");
console.log("╚══════════════════════════════════════════════════════╝\n");

await counterA.write.inc();
await counterB.write.inc();

// ═══════════════════════════════════════════════════════════════════════
// 3. Multi-connection — concurrent txs on both networks
// Traces from each connection are atomic (no interleaving)
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ 3 — Multi-connection, concurrent (3 txs each) ║");
console.log("╚══════════════════════════════════════════════════════╝\n");

await Promise.all([
counterA.write.inc(),
counterA.write.inc(),
counterA.write.inc(),
counterB.write.inc(),
counterB.write.inc(),
counterB.write.inc(),
]);

// ═══════════════════════════════════════════════════════════════════════
// 4. Batch mining with evm_mine — grouped traces under a single header
// "Traces from connection #N (network): evm_mine" (plural)
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ 4 — evm_mine: 5 txs in one block (grouping demo) ║");
console.log("╚══════════════════════════════════════════════════════╝\n");
console.log(' → expect 1 "Traces from" header + 5 traces below it\n');

await connA.provider.request({ method: "evm_setAutomine", params: [false] });
await Promise.all(Array.from({ length: 5 }, () => counterA.write.inc()));
await connA.provider.request({ method: "evm_mine", params: [] });
await connA.provider.request({ method: "evm_setAutomine", params: [true] });

// ═══════════════════════════════════════════════════════════════════════
// 5. Failed RPC calls — header should be red, method name highlighted
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ 5 — Failing calls (red header / method name) ║");
console.log("╚══════════════════════════════════════════════════════╝\n");

// 5a. Revert contract with known error message
console.log(" 5a. Revert.boom() — reverts with 'Boom':\n");
try {
await revertContract.read.boom();
} catch (e: any) {
console.log(` → Reverted: ${e.details ?? e.message}\n`);
}

// 5b. Invalid selector on Counter (no fallback)
console.log(" 5b. Invalid selector 0xdeadbeef on Counter:\n");
try {
await connA.provider.request({
method: "eth_call",
params: [{ to: counterA.address, data: "0xdeadbeef" }, "latest"],
});
} catch {
console.log(" → Reverted as expected\n");
}

// 5c. Orchestrator.doCallThatReverts(0) — nested revert
// Deploy fresh orchestrator since the earlier doDelegateCall corrupted storage
console.log(" 5c. Orchestrator.doCallThatReverts(0) — nested revert:\n");
const logic2 = await connA.viem.deployContract("Logic", []);
const orch2 = await connA.viem.deployContract("Orchestrator", [logic2.address]);
try {
await orch2.write.doCallThatReverts([0n]);
} catch (e: any) {
console.log(` → Reverted: ${e.details ?? e.message}\n`);
}

// ═══════════════════════════════════════════════════════════════════════
// 6. Deduplication — single write triggers estimateGas + sendTx + receipt
// but only ONE trace should appear
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ 6 — Deduplication ║");
console.log("╚══════════════════════════════════════════════════════╝\n");
console.log(" A single counter.write.inc() triggers 3 RPC calls:");
console.log(" 1. eth_estimateGas → suppressed at -vvvv, shown at -vvvvv");
console.log(" 2. eth_sendTransaction → always shown (this is the real tx)");
console.log(" 3. eth_getTransactionReceipt → no traces (read-only)");
console.log(
" At -vvvv: 1 trace. At -vvvvv: 2 traces (estimateGas + sendTx).\n",
);

await counterA.write.inc();

// ═══════════════════════════════════════════════════════════════════════
// Summary
// ═══════════════════════════════════════════════════════════════════════
console.log("\n╔══════════════════════════════════════════════════════╗");
console.log("║ Summary ║");
console.log("╚══════════════════════════════════════════════════════╝");
console.log("");
console.log(" -vvvv: dedup + suppression active (1 trace per write)");
console.log(" -vvvvv: no dedup (estimateGas + sendTx both shown)");
console.log("");
console.log("Done.");
Loading
Loading