Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gas report #104

Merged
merged 9 commits into from
Aug 19, 2023
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
34 changes: 34 additions & 0 deletions src/_modules/Context.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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 name 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()));
Copy link

@holic holic Apr 16, 2023

Choose a reason for hiding this comment

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

ooc do these operations affect gasleft calculation? I assume gasleft() is computed first on this line, then put in storage, so I'm wondering if this storage operation would be the first gas consumed in this report?

(I am totally fine with this for the purposes of #103 but just curious!)

}

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 {
Expand Down Expand Up @@ -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) {
Expand Down
46 changes: 46 additions & 0 deletions src/_modules/Gas.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "./Vulcan.sol";
import "./Accounts.sol";

library gas {
bytes32 constant GAS_MEASUREMENTS_MAGIC = keccak256("vulcan.gas.measurements.magic");

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 stopRecord(string memory name) internal returns (uint256) {
uint256 endGas = gasleft();

bytes32 startSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "start"));
uint256 startGas = uint256(accounts.readStorage(address(vulcan.hevm), startSlot));

if (endGas > startGas) {
revert("gas.stopRecord: Gas used can't have a negative value");
}

bytes32 endSlot = keccak256(abi.encode(GAS_MEASUREMENTS_MAGIC, name, "end"));
accounts.setStorage(address(vulcan.hevm), endSlot, bytes32(endGas));

return startGas - endGas;
}

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;
}
}
1 change: 1 addition & 0 deletions src/test.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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} from "./_modules/Gas.sol";
import {huff, Huffc} from "./_modules/Huff.sol";
import {json, JsonObject} from "./_modules/Json.sol";
import {strings} from "./_modules/Strings.sol";
Expand Down
8 changes: 8 additions & 0 deletions test/_modules/Context.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,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();
Copy link

@holic holic Apr 16, 2023

Choose a reason for hiding this comment

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

re: above, I'd be curious to see a similar measurement done without the context methods to see how they compare gas-wise (to determine if and how much gas is added by the gas reporting calls themselves), e.g.

uint256 startGas = gasleft();
for (uint256 i = 0; i < 5; i++) {
    new MockTarget();
}
println("gas used:", startGas - gasleft());

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry for the delay, we are in the middle of an audit right now, but will definitely look into this next week

Copy link
Contributor

@gnkz gnkz May 22, 2023

Choose a reason for hiding this comment

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

Hey @holic . This is the gas comparison between the vulcan method vs using gasleft

| test/_modules/Context.t.sol:GasReport contract |                 |        |        |        |         |
|------------------------------------------------|-----------------|--------|--------|--------|---------|
| Deployment Cost                                | Deployment Size |        |        |        |         |
| 488124                                         | 2470            |        |        |        |         |
| Function Name                                  | min             | avg    | median | max    | # calls |
| nativeReport                                   | 305575          | 305575 | 305575 | 305575 | 1       |
| vulcanReport                                   | 313671          | 313671 | 313671 | 313671 | 1       |

And these are the measurements:

  • Vulcan: 304141
  • Native: 303650

This is the contract we used to compare

contract GasReport {
    function vulcanReport() public {
        ctx.startGasReport("test");
        for (uint256 i = 0; i < 5; i++) {
            new MockTarget();
        }
        ctx.endGasReport();
    }

    function nativeReport() public {
        uint256 start = gasleft();
        for (uint256 i = 0; i < 5; i++) {
            new MockTarget();
        }
        println(string.concat("gas(test)", strings.toString(start - gasleft())));
    }
}

}
}

contract MockTarget {
Expand Down
17 changes: 17 additions & 0 deletions test/_modules/Gas.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import {Test, expect, gas} from "../../src/test.sol";

contract GasTest is Test {
function testItMeasures() public {
string memory name = "test";

gas.record(name);
keccak256(bytes(name));
uint256 measurementValue = gas.stopRecord(name);

expect(measurementValue).toBeGreaterThan(0);
expect(measurementValue).toEqual(gas.used(name));
}
}