From da402b32f24daa9d868baeb6a589aab30599d6c6 Mon Sep 17 00:00:00 2001 From: Vicente Dragicevic Date: Tue, 11 Apr 2023 17:25:32 -0400 Subject: [PATCH 1/5] Add ctx functions for gas reporting --- src/_modules/Context.sol | 34 ++++++++++++++++++++++++++++++++++ test/_modules/Context.t.sol | 8 ++++++++ 2 files changed, 42 insertions(+) diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index 0ad5d4bd..a1eadd91 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -3,6 +3,8 @@ pragma solidity >=0.8.13 <0.9.0; import "./Vulcan.sol"; import "./Accounts.sol"; +import "./Strings.sol"; +import "../_utils/println.sol"; type Context is bytes32; @@ -69,6 +71,30 @@ library ctxSafe { function resumeGasMetering() internal { vulcan.hevm.resumeGasMetering(); } + + function startGasReport(string memory name) internal { + if (bytes(name).length > 32) { + revert("ctx.startGasReport: Gas report length can't have more than 32 characters"); + } + + bytes32 b32Name = bytes32(bytes(name)); + bytes32 slot = keccak256(bytes("vulcan.ctx.gasReport.name")); + accounts.setStorage(address(vulcan.hevm), slot, b32Name); + bytes32 valueSlot = keccak256(abi.encodePacked("vulcan.ctx.gasReport", b32Name)); + accounts.setStorage(address(vulcan.hevm), valueSlot, bytes32(gasleft())); + } + + function endGasReport() internal view { + uint256 gas = gasleft(); + bytes32 slot = keccak256(bytes("vulcan.ctx.gasReport.name")); + bytes32 b32Name = accounts.readStorage(address(vulcan.hevm), slot); + bytes32 valueSlot = keccak256(abi.encodePacked("vulcan.ctx.gasReport", b32Name)); + uint256 prevGas = uint256(accounts.readStorage(address(vulcan.hevm), valueSlot)); + if (gas > prevGas) { + revert("ctx.endGasReport: Gas used can't have a negative value"); + } + println(string.concat("gas(", string(abi.encodePacked(b32Name)), "):", strings.toString(prevGas - gas))); + } } library ctx { @@ -120,6 +146,14 @@ library ctx { ctxSafe.resumeGasMetering(); } + function startGasReport(string memory name) internal { + ctxSafe.startGasReport(name); + } + + function endGasReport() internal view { + ctxSafe.endGasReport(); + } + /// @dev Checks whether the current call is a static call or not. /// @return True if the current call is a static call, false otherwise. function isStaticcall() internal view returns (bool) { diff --git a/test/_modules/Context.t.sol b/test/_modules/Context.t.sol index 925a53cf..4f382634 100644 --- a/test/_modules/Context.t.sol +++ b/test/_modules/Context.t.sol @@ -100,6 +100,14 @@ contract ContextTest is Test { target.value{value: uint256(1337)}(); } + + function testItCanReportGas() external { + ctx.startGasReport("test"); + for (uint256 i = 0; i < 5; i++) { + new MockTarget(); + } + ctx.endGasReport(); + } } contract MockTarget { From 3ae4c64443e6133307953f78c04fb429b1cdd6bb Mon Sep 17 00:00:00 2001 From: Vicente Dragicevic Date: Tue, 11 Apr 2023 17:53:28 -0400 Subject: [PATCH 2/5] Typo --- src/_modules/Context.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index a1eadd91..99c4f4f6 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -74,7 +74,7 @@ library ctxSafe { function startGasReport(string memory name) internal { if (bytes(name).length > 32) { - revert("ctx.startGasReport: Gas report length can't have more than 32 characters"); + revert("ctx.startGasReport: Gas report name can't have more than 32 characters"); } bytes32 b32Name = bytes32(bytes(name)); From 8f202f772155f3f94e6d287348af283b2e766e0b Mon Sep 17 00:00:00 2001 From: gnkz Date: Thu, 8 Jun 2023 09:50:15 -0400 Subject: [PATCH 3/5] feat: add gas module --- src/_modules/Gas.sol | 50 +++++++++++++++++++++++++++++++++++++++++ src/test.sol | 1 + test/_modules/Gas.t.sol | 17 ++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 src/_modules/Gas.sol create mode 100644 test/_modules/Gas.t.sol diff --git a/src/_modules/Gas.sol b/src/_modules/Gas.sol new file mode 100644 index 00000000..d50aa2d5 --- /dev/null +++ b/src/_modules/Gas.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import "./Vulcan.sol"; +import "./Accounts.sol"; + +struct GasMeasurement { + uint256 start; + uint256 end; +} + +library gas { + bytes32 constant GAS_MEASUREMENTS_SLOT = keccak256("vulcan.gas.measurements"); + + function measurements() internal pure returns (mapping(string => GasMeasurement) storage m) { + bytes32 slot = GAS_MEASUREMENTS_SLOT; + + assembly { + m.slot := slot + } + } + + function measurement(string memory name) internal view returns (GasMeasurement memory) { + return measurements()[name]; + } + + function measure(string memory name) internal { + measurements()[name] = GasMeasurement(gasleft(), uint256(0)); + } + + function endMeasure(string memory name) internal returns (uint256) { + uint256 endGas = gasleft(); + + uint256 startGas = measurement(name).start; + + if (endGas > startGas) { + revert("gas.endMeasure: Gas used can't have a negative value"); + } + + measurements()[name].end = endGas; + + return startGas - endGas; + } + + function value(GasMeasurement memory gasMeasurement) internal pure returns (uint256) { + return gasMeasurement.start - gasMeasurement.end; + } +} + +using gas for GasMeasurement global; diff --git a/src/test.sol b/src/test.sol index 7cb9a5fc..37716fa0 100644 --- a/src/test.sol +++ b/src/test.sol @@ -12,6 +12,7 @@ import {events} from "./_modules/Events.sol"; import {expect} from "./_modules/Expect.sol"; import {forks, Fork} from "./_modules/Forks.sol"; import {fs, FsMetadata} from "./_modules/Fs.sol"; +import {gas, GasMeasurement} from "./_modules/Gas.sol"; import {huff, Huffc} from "./_modules/Huff.sol"; import {json, JsonObject} from "./_modules/Json.sol"; import {strings} from "./_modules/Strings.sol"; diff --git a/test/_modules/Gas.t.sol b/test/_modules/Gas.t.sol new file mode 100644 index 00000000..7017f104 --- /dev/null +++ b/test/_modules/Gas.t.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {Test, expect, gas, GasMeasurement} from "../../src/test.sol"; + +contract GasTest is Test { + function testItMeasures() public { + string memory name = "test"; + + gas.measure(name); + keccak256(bytes(name)); + uint256 measurementValue = gas.endMeasure(name); + + expect(measurementValue).toBeGreaterThan(0); + expect(measurementValue).toEqual(gas.measurement(name).value()); + } +} From ac69fb39589fa130fa7a714c9051aeacd25a8d97 Mon Sep 17 00:00:00 2001 From: gnkz Date: Thu, 8 Jun 2023 14:21:39 -0400 Subject: [PATCH 4/5] chore: update gas module --- src/_modules/Gas.sol | 51 +++++++++++++++++++---------------------- src/test.sol | 2 +- test/_modules/Gas.t.sol | 8 +++---- 3 files changed, 29 insertions(+), 32 deletions(-) diff --git a/src/_modules/Gas.sol b/src/_modules/Gas.sol index d50aa2d5..31386049 100644 --- a/src/_modules/Gas.sol +++ b/src/_modules/Gas.sol @@ -4,47 +4,44 @@ pragma solidity ^0.8.17; import "./Vulcan.sol"; import "./Accounts.sol"; -struct GasMeasurement { - uint256 start; - uint256 end; -} - library gas { - bytes32 constant GAS_MEASUREMENTS_SLOT = keccak256("vulcan.gas.measurements"); - - function measurements() internal pure returns (mapping(string => GasMeasurement) storage m) { - bytes32 slot = GAS_MEASUREMENTS_SLOT; - - assembly { - m.slot := slot - } - } - - function measurement(string memory name) internal view returns (GasMeasurement memory) { - return measurements()[name]; - } + bytes32 constant GAS_MEASUREMENTS_MAGIC = keccak256("vulcan.gas.measurements.magic"); - function measure(string memory name) internal { - measurements()[name] = GasMeasurement(gasleft(), uint256(0)); + function record(string memory name) internal { + bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start")); + accounts.setStorage(address(vulcan.hevm), startSlot, bytes32(gasleft())); } - function endMeasure(string memory name) internal returns (uint256) { + function stopRecord(string memory name) internal returns (uint256) { uint256 endGas = gasleft(); - uint256 startGas = measurement(name).start; + bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start")); + uint256 startGas = uint256(accounts.readStorage(address(vulcan.hevm), startSlot)); if (endGas > startGas) { - revert("gas.endMeasure: Gas used can't have a negative value"); + revert("gas.stopRecord: Gas used can't have a negative value"); } - measurements()[name].end = endGas; + bytes32 endSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "end")); + accounts.setStorage(address(vulcan.hevm), endSlot, bytes32(endGas)); return startGas - endGas; } - function value(GasMeasurement memory gasMeasurement) internal pure returns (uint256) { - return gasMeasurement.start - gasMeasurement.end; + function getRecord(string memory name) internal view returns (uint256, uint256) { + bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start")); + uint256 startGas = uint256(accounts.readStorage(address(vulcan.hevm), startSlot)); + + bytes32 endSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "end")); + uint256 endGas = uint256(accounts.readStorage(address(vulcan.hevm), endSlot)); + + return (startGas, endGas); + } + + function used(string memory name) internal view returns (uint256) { + (uint256 startGas, uint256 endGas) = getRecord(name); + + return startGas - endGas; } } -using gas for GasMeasurement global; diff --git a/src/test.sol b/src/test.sol index 37716fa0..2ba35e69 100644 --- a/src/test.sol +++ b/src/test.sol @@ -12,7 +12,7 @@ import {events} from "./_modules/Events.sol"; import {expect} from "./_modules/Expect.sol"; import {forks, Fork} from "./_modules/Forks.sol"; import {fs, FsMetadata} from "./_modules/Fs.sol"; -import {gas, GasMeasurement} from "./_modules/Gas.sol"; +import {gas} from "./_modules/Gas.sol"; import {huff, Huffc} from "./_modules/Huff.sol"; import {json, JsonObject} from "./_modules/Json.sol"; import {strings} from "./_modules/Strings.sol"; diff --git a/test/_modules/Gas.t.sol b/test/_modules/Gas.t.sol index 7017f104..c2738355 100644 --- a/test/_modules/Gas.t.sol +++ b/test/_modules/Gas.t.sol @@ -1,17 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {Test, expect, gas, GasMeasurement} from "../../src/test.sol"; +import {Test, expect, gas} from "../../src/test.sol"; contract GasTest is Test { function testItMeasures() public { string memory name = "test"; - gas.measure(name); + gas.record(name); keccak256(bytes(name)); - uint256 measurementValue = gas.endMeasure(name); + uint256 measurementValue = gas.stopRecord(name); expect(measurementValue).toBeGreaterThan(0); - expect(measurementValue).toEqual(gas.measurement(name).value()); + expect(measurementValue).toEqual(gas.used(name)); } } From bc98fff2290ce3f49ab5cc4c119afdf2831f5397 Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 28 Jul 2023 12:19:46 -0400 Subject: [PATCH 5/5] style: format --- src/_modules/Gas.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/_modules/Gas.sol b/src/_modules/Gas.sol index 31386049..23abfd62 100644 --- a/src/_modules/Gas.sol +++ b/src/_modules/Gas.sol @@ -44,4 +44,3 @@ library gas { return startGas - endGas; } } -