From da402b32f24daa9d868baeb6a589aab30599d6c6 Mon Sep 17 00:00:00 2001 From: Vicente Dragicevic Date: Tue, 11 Apr 2023 17:25:32 -0400 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 05/17] 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; } } - From f58f41a6b270ea401f9f4d9a48055980ce17b424 Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 28 Jul 2023 14:20:43 -0400 Subject: [PATCH 06/17] chore: remove forge-std --- .gitmodules | 3 --- lib/forge-std | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules index 888d42dc..e69de29b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "lib/forge-std"] - path = lib/forge-std - url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std deleted file mode 160000 index 2b58ecbc..00000000 --- a/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2b58ecbcf3dfde7a75959dc7b4eb3d0670278de6 From 71710ebd8923d8e1b65a780f2404d3ae56d6369d Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 28 Jul 2023 14:20:49 -0400 Subject: [PATCH 07/17] forge install: forge-std v1.6.0 --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules index e69de29b..888d42dc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..74cfb77e --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 From 245fe45aca096deccd37cdc924dd06a4a62374af Mon Sep 17 00:00:00 2001 From: gnkz Date: Sat, 29 Jul 2023 10:46:38 -0400 Subject: [PATCH 08/17] fix: remove warnings --- src/_modules/Fs.sol | 4 ++-- src/_utils/println.sol | 4 ++-- test/ExampleTest.sol | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/_modules/Fs.sol b/src/_modules/Fs.sol index 8a7e0f60..88486b53 100644 --- a/src/_modules/Fs.sol +++ b/src/_modules/Fs.sol @@ -38,7 +38,7 @@ library fs { /// @dev Obtains the metadata of the specified file or directory. /// @param fileOrDir The path to the file or directory. /// @return data The metadata of the file or directory. - function metadata(string memory fileOrDir) internal returns (FsMetadata memory data) { + function metadata(string memory fileOrDir) internal view returns (FsMetadata memory data) { Hevm.FsMetadata memory md = vulcan.hevm.fsMetadata(fileOrDir); assembly { data := md @@ -103,7 +103,7 @@ library fs { /// @dev Checks if a file or directory exists. /// @param path The file or directory to check. /// @return Whether the file on `path` exists or not. - function fileExists(string memory path) internal returns (bool) { + function fileExists(string memory path) internal view returns (bool) { try vulcan.hevm.fsMetadata(path) { return true; } catch Error(string memory) { diff --git a/src/_utils/println.sol b/src/_utils/println.sol index 85995568..5ee09668 100644 --- a/src/_utils/println.sol +++ b/src/_utils/println.sol @@ -4,10 +4,10 @@ pragma solidity >=0.8.13 <0.9.0; import {console} from "../_modules/Console.sol"; import {fmt} from "../_modules/Fmt.sol"; -function println(string memory template, bytes memory args) view { +function println(string memory template, bytes memory args) pure { console.log(fmt.format(template, args)); } -function println(string memory arg) view { +function println(string memory arg) pure { console.log(arg); } diff --git a/test/ExampleTest.sol b/test/ExampleTest.sol index 33b18c48..6ade669a 100644 --- a/test/ExampleTest.sol +++ b/test/ExampleTest.sol @@ -21,7 +21,7 @@ contract ExampleTest is Test { expect(false).toEqual(false); } - function testConsoleLog() external view { + function testConsoleLog() external pure { console.log("hello world"); } From 32b71c37d708d9a884c1d5d76bb32c571be35ab2 Mon Sep 17 00:00:00 2001 From: gnkz Date: Thu, 17 Aug 2023 16:52:16 -0400 Subject: [PATCH 09/17] feat: add missing forge-std functions --- src/_modules/Accounts.sol | 9 ++++++++ src/_modules/Context.sol | 46 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/src/_modules/Accounts.sol b/src/_modules/Accounts.sol index 1951a2a5..27cfbe09 100644 --- a/src/_modules/Accounts.sol +++ b/src/_modules/Accounts.sol @@ -186,6 +186,15 @@ library accounts { return self; } + /// @dev Sets the nonce of the given `self` address to the arbitrary provided value `n`. + /// @param self The address to set the nonce for. + /// @param n The value to set the nonce to. + /// @return The updated address with the modified nonce. + function setArbitraryNonce(address self, uint64 n) internal returns (address) { + vulcan.hevm.setNonceUnsafe(self, n); + return self; + } + /// @dev Sets the `msg.sender` of the next call to `self`. /// @param self The address to impersonate. /// @return The address that was impersonated. diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index cda6349f..4a1ff38a 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -165,6 +165,13 @@ library ctx { return setBlockBaseFee(Context.wrap(0), baseFee); } + /// @dev Sets block.prevrandao. + /// @param newPrevrandao The new `block.prevrandao`. + function setBlockPrevrandao(Context self, bytes32 newPrevrandao) internal returns (Context) { + vulcan.hevm.prevrandao(newPrevrandao); + return self; + } + /// @dev sets the `block.chainid` to `chainId` /// @param chainId the new block chain id function setChainId(Context self, uint64 chainId) internal returns (Context) { @@ -194,6 +201,13 @@ library ctx { return setBlockCoinbase(Context.wrap(0), who); } + /// @dev Sets the transaction gas price. + /// @param newGasPrice The new transaction gas price. + function setGasPrice(Context self, uint256 newGasPrice) internal returns (Context) { + vulcan.hevm.txGasPrice(newGasPrice); + return self; + } + /// @dev Function used to check whether the next call reverts or not. /// @param revertData The function call data that that is expected to fail. function expectRevert(bytes memory revertData) internal { @@ -269,6 +283,23 @@ library ctx { vulcan.hevm.expectCall(callee, msgValue, data); } + + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) internal { + vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data); + } + + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external { + vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data, count); + } + + function expectSafeMemory(uint64 min, uint64 max) external { + vulcan.hevm.expectSafeMemory(min, max); + } + + function expectSafeMemoryCall(uint64 min, uint64 max) external { + vulcan.hevm.expectSafeMemoryCall(min, max); + } + /// @dev Takes a snapshot of the current state of the vm and returns an identifier. /// @return The snapshot identifier. function snapshot(Context) internal returns (uint256) { @@ -294,6 +325,21 @@ library ctx { function revertToSnapshot(uint256 snapshotId) internal returns (bool) { return revertToSnapshot(Context.wrap(0), snapshotId); } + + /// @dev Creates a breakpoint to jump to in the debugger. + /// @param name The name of the breakpoint. + function addBreakpoint(Context self, string memory name) internal returns (Context) { + vulcan.hevm.breakpoint(name); + return self; + } + + /// @dev Creates a breakpoint to jump to in the debugger. + /// @param name The name of the breakpoint. + /// @param condition The condition that needs to be fulfilled in order to add the breakpoint. + function addConditionalBreakpoint(Context self, string memory name, bool condition) internal returns (Context) { + vulcan.hevm.breakpoint(name, condition); + return self; + } } using ctx for Context global; From 6c1e9865f55d0fb8c12df5cfc52fe197faacf609 Mon Sep 17 00:00:00 2001 From: gnkz Date: Thu, 17 Aug 2023 16:54:19 -0400 Subject: [PATCH 10/17] style: fmt --- src/_modules/Context.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index 4a1ff38a..b61fd3a5 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -283,12 +283,13 @@ library ctx { vulcan.hevm.expectCall(callee, msgValue, data); } - function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) internal { vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data); } - function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external { + function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) + external + { vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data, count); } From 9c028c29d7a2d0ac5e7dac0c6c0dcd81379fdbf2 Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 18 Aug 2023 14:41:33 -0400 Subject: [PATCH 11/17] docs: add natspect for the new methods --- src/_modules/Accounts.sol | 5 +++-- src/_modules/Context.sol | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/_modules/Accounts.sol b/src/_modules/Accounts.sol index 27cfbe09..ac6aa368 100644 --- a/src/_modules/Accounts.sol +++ b/src/_modules/Accounts.sol @@ -177,7 +177,8 @@ library accounts { return self; } - /// @dev Sets the nonce of the given `self` address to the provided value `n`. + /// @dev Sets the nonce of the given `self` address to the provided value `n`. It will revert if + // the new nonce is lower than the current address nonce. /// @param self The address to set the nonce for. /// @param n The value to set the nonce to. /// @return The updated address with the modified nonce. @@ -190,7 +191,7 @@ library accounts { /// @param self The address to set the nonce for. /// @param n The value to set the nonce to. /// @return The updated address with the modified nonce. - function setArbitraryNonce(address self, uint64 n) internal returns (address) { + function setNonceUnsafe(address self, uint64 n) internal returns (address) { vulcan.hevm.setNonceUnsafe(self, n); return self; } diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index b61fd3a5..8ff77bdd 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -283,20 +283,39 @@ library ctx { vulcan.hevm.expectCall(callee, msgValue, data); } + /// @dev Expect a call to an address with the specified msg.value and calldata, and a minimum amount of gas. + /// @param callee The address that is expected to be called. + /// @param msgValue The `msg.value` that is expected to be sent. + /// @param minGas The expected minimum amount of gas for the call. + /// @param data The call data that is expected to be used. function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data) internal { vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data); } + /// @dev Expect a call to an address with the specified msg.value and calldata, and a minimum amount of gas. + /// @param callee The address that is expected to be called. + /// @param msgValue The `msg.value` that is expected to be sent. + /// @param minGas The expected minimum amount of gas for the call. + /// @param data The call data that is expected to be used. + /// @param count The number of calls that are expected. function expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count) external { vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data, count); } + /// @dev Allows to write on memory only between [0x00, 0x60) and [min, max) in the current + /// subcontext. + /// @param min The lower limit of the allowed memory slot. + /// @param max The upper limit of the allowed memory slot. function expectSafeMemory(uint64 min, uint64 max) external { vulcan.hevm.expectSafeMemory(min, max); } + /// @dev Allows to write on memory only between [0x00, 0x60) and [min, max) in the next + // subcontext. + /// @param min The lower limit of the allowed memory slot. + /// @param max The upper limit of the allowed memory slot. function expectSafeMemoryCall(uint64 min, uint64 max) external { vulcan.hevm.expectSafeMemoryCall(min, max); } From 522b1fa7d13e6ac7ee0f9d13ad60dfd89a1c001e Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 18 Aug 2023 14:57:30 -0400 Subject: [PATCH 12/17] feat: adds the `setPrevrandao` method without context --- src/_modules/Context.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index 8ff77bdd..446f3a8a 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -172,6 +172,12 @@ library ctx { return self; } + /// @dev Sets block.prevrandao. + /// @param newPrevrandao The new `block.prevrandao`. + function setBlockPrevrandao(bytes32 newPrevrandao) internal returns (Context) { + return setBlockPrevrandao(Context.wrap(0), newPrevrandao); + } + /// @dev sets the `block.chainid` to `chainId` /// @param chainId the new block chain id function setChainId(Context self, uint64 chainId) internal returns (Context) { From a9f419390c95e0d42ef1dc106fbb2952fd06f02a Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 18 Aug 2023 14:59:57 -0400 Subject: [PATCH 13/17] feat: adds the `setGrasPrice` method without Context --- src/_modules/Context.sol | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index 446f3a8a..8d149ee0 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -214,6 +214,12 @@ library ctx { return self; } + /// @dev Sets the transaction gas price. + /// @param newGasPrice The new transaction gas price. + function setGasPrice(uint256 newGasPrice) internal returns (Context) { + return setGasPrice(Context.wrap(0), newGasPrice); + } + /// @dev Function used to check whether the next call reverts or not. /// @param revertData The function call data that that is expected to fail. function expectRevert(bytes memory revertData) internal { From 0900adc7de2c7925a17a8f5d6ca432f51d72e43e Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 18 Aug 2023 15:15:02 -0400 Subject: [PATCH 14/17] docs: fix natspec --- src/_modules/Context.sol | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/_modules/Context.sol b/src/_modules/Context.sol index 8d149ee0..00029eae 100644 --- a/src/_modules/Context.sol +++ b/src/_modules/Context.sol @@ -304,7 +304,7 @@ library ctx { vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data); } - /// @dev Expect a call to an address with the specified msg.value and calldata, and a minimum amount of gas. + /// @dev Expect a number call to an address with the specified msg.value and calldata, and a minimum amount of gas. /// @param callee The address that is expected to be called. /// @param msgValue The `msg.value` that is expected to be sent. /// @param minGas The expected minimum amount of gas for the call. @@ -316,7 +316,7 @@ library ctx { vulcan.hevm.expectCallMinGas(callee, msgValue, minGas, data, count); } - /// @dev Allows to write on memory only between [0x00, 0x60) and [min, max) in the current + /// @dev Allows to write on memory only between [0x00, 0x60) and [min, max) in the current. /// subcontext. /// @param min The lower limit of the allowed memory slot. /// @param max The upper limit of the allowed memory slot. @@ -328,7 +328,7 @@ library ctx { // subcontext. /// @param min The lower limit of the allowed memory slot. /// @param max The upper limit of the allowed memory slot. - function expectSafeMemoryCall(uint64 min, uint64 max) external { + function expectsafememorycall(uint64 min, uint64 max) external { vulcan.hevm.expectSafeMemoryCall(min, max); } @@ -365,6 +365,12 @@ library ctx { return self; } + /// @dev Creates a breakpoint to jump to in the debugger. + /// @param name The name of the breakpoint. + function addBreakpoint(string memory name) internal returns (Context) { + return addBreakpoint(Context.wrap(0), name); + } + /// @dev Creates a breakpoint to jump to in the debugger. /// @param name The name of the breakpoint. /// @param condition The condition that needs to be fulfilled in order to add the breakpoint. @@ -372,6 +378,13 @@ library ctx { vulcan.hevm.breakpoint(name, condition); return self; } + + /// @dev Creates a breakpoint to jump to in the debugger. + /// @param name The name of the breakpoint. + /// @param condition The condition that needs to be fulfilled in order to add the breakpoint. + function addConditionalBreakpoint(string memory name, bool condition) internal returns (Context) { + return addConditionalBreakpoint(Context.wrap(0), name, condition); + } } using ctx for Context global; From bf61bf5ad575910210fe938569b18341f33c1541 Mon Sep 17 00:00:00 2001 From: gnkz Date: Fri, 18 Aug 2023 15:15:19 -0400 Subject: [PATCH 15/17] docs: add book docs --- docs/src/reference/modules/accounts.md | 7 +++- docs/src/reference/modules/context.md | 48 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/src/reference/modules/accounts.md b/docs/src/reference/modules/accounts.md index e08dddee..849c6cca 100644 --- a/docs/src/reference/modules/accounts.md +++ b/docs/src/reference/modules/accounts.md @@ -54,7 +54,12 @@ Sets the specified `slot` in the storage of the given `self` address to the prov #### **`setNonce(address self, uint64 n) → (address)`** -Sets the nonce of the given `self` address to the provided value `n`. +Sets the nonce of the given `self` address to the provided value `n`. It will revert if the new +nonce is lower than the current address nonce. + +#### **`setNonceUnsafe(address self, uint64 n) → (address)`** + +Sets the nonce of the given `self` address to the provided arbitrary value `n`. #### **`impersonateOnce(address self) → (address)`** diff --git a/docs/src/reference/modules/context.md b/docs/src/reference/modules/context.md index 68809685..e25f1ffa 100644 --- a/docs/src/reference/modules/context.md +++ b/docs/src/reference/modules/context.md @@ -57,6 +57,14 @@ sets the `block.basefee` to `baseFee` sets the `block.basefee` to `baseFee` +#### **`setBlockPrevrandao(Context self, bytes32 newPrevrandao) → (Context)`** + +sets the `block.prevrandao` to `newPrevrandao` + +#### **`setBlockPrevrandao(bytes32 newPrevrandao) → (Context)`** + +sets the `block.prevrandao` to `newPrevrandao` + #### **`setChainId(Context self, uint64 chainId) → (Context)`** sets the `block.chainid` to `chainId` @@ -73,6 +81,14 @@ Sets the block coinbase to `who`. Sets the block coinbase to `who`. +#### **`setGasPrice(Context self, address newGasPrice) → (Context)`** + +Sets the gas price to `newGasPrice`. + +#### **`setGasPrice(address newGasPrice) → (Context)`** + +Sets the gas price to `newGasPrice`. + #### **`expectRevert(bytes revertData)`** Function used to check whether the next call reverts or not. @@ -113,6 +129,22 @@ Used to check if a call to `callee` with `data` was made. Used to check if a call to `callee` with `data` and `msgValue` was made. +#### **`expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data)`** + +Expect a call from `callee` with the specified `msgValue` and `data`, and a minimum amount of gas `minGas`. + +#### **`expectCallMinGas(address callee, uint256 msgValue, uint64 minGas, bytes calldata data, uint64 count)`** + +Expect a number of calls `count` from `callee` with the specified `msgValue` and `data`, and a minimum amount of gas `minGas`. + +#### **`expectSafeMemory(uint64 min, uint64 max)`** + +Allows to write on memory only between [0x00, 0x60) and [`min`, `max`) in the current subcontext + +#### **`expectsafememorycall(uint64 min, uint64 max)`** + +Allows to write on memory only between [0x00, 0x60) and [`min`, `max`) in the next subcontext + #### **`snapshot(Context) → (uint256)`** Takes a snapshot of the current state of the vm and returns an identifier. @@ -129,3 +161,19 @@ Reverts the state of the vm to the snapshot with id `snapshotId`. Reverts the state of the vm to the snapshot with id `snapshotId`. +#### **`addBreakpoint(Context self, string memory name)`** + +Creates a breakpoint to jump to in the debugger with `name`. + +#### **`addBreakpoint(string memory name)`** + +Creates a breakpoint to jump to in the debugger with `name`. + +#### **`addConditionalBreakpoint(Context self, string memory name, bool condition)`** + +Creates a conditional breakpoint to jump to in the debugger with name `name` and condition `condition`. + +#### **`addConditionalBreakpoint(string memory name, bool condition)`** + +Creates a conditional breakpoint to jump to in the debugger with name `name` and condition `condition`. + From 745b90bcd0a75b92836f98ec2e59a5cbdffd092f Mon Sep 17 00:00:00 2001 From: gnkz Date: Sat, 19 Aug 2023 18:02:37 -0400 Subject: [PATCH 16/17] feat: add function to calculate deployment addresses --- src/_modules/Accounts.sol | 45 ++++++++++++++++++++++++++++++++++++ test/_modules/Accounts.t.sol | 28 +++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/_modules/Accounts.sol b/src/_modules/Accounts.sol index ac6aa368..38c9a79b 100644 --- a/src/_modules/Accounts.sol +++ b/src/_modules/Accounts.sol @@ -102,6 +102,43 @@ library accountsSafe { return label(addr, lbl); } + + /// @dev Calculates the deployment address of `who` with nonce `nonce`. + /// @param who The deployer address. + /// @param nonce The deployer nonce. + function getDeploymentAddress(address who, uint64 nonce) internal pure returns (address) { + bytes memory data; + + if (nonce == 0x00) { + data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), who, bytes1(0x80)); + } else if (nonce <= 0x7f) { + data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), who, uint8(nonce)); + } else if (nonce <= 0xff) { + data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), who, bytes1(0x81), uint8(nonce)); + } else if (nonce <= 0xffff) { + data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), who, bytes1(0x82), uint16(nonce)); + } else if (nonce <= 0xffffff) { + data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), who, bytes1(0x83), uint24(nonce)); + } else if (nonce <= 0xffffffff) { + data = abi.encodePacked(bytes1(0xda), bytes1(0x94), who, bytes1(0x84), uint32(nonce)); + } else if (nonce <= 0xffffffffff) { + data = abi.encodePacked(bytes1(0xdb), bytes1(0x94), who, bytes1(0x85), uint40(nonce)); + } else if (nonce <= 0xffffffffffff) { + data = abi.encodePacked(bytes1(0xdc), bytes1(0x94), who, bytes1(0x86), uint48(nonce)); + } else if (nonce <= 0xffffffffffffff) { + data = abi.encodePacked(bytes1(0xdd), bytes1(0x94), who, bytes1(0x87), uint56(nonce)); + } else if (nonce <= 0xffffffffffffffff) { + data = abi.encodePacked(bytes1(0xde), bytes1(0x94), who, bytes1(0x88), uint64(nonce)); + } + + return address(uint160(uint256(keccak256(data)))); + } + + /// @dev Calculates the deployment address of `who` with the current nonce. + /// @param who The deployer address. + function getDeploymentAddress(address who) internal view returns (address) { + return getDeploymentAddress(who, getNonce(who)); + } } library accounts { @@ -167,6 +204,14 @@ library accounts { return accountsSafe.create(name, lbl); } + function getDeploymentAddress(address who, uint64 nonce) internal pure returns (address) { + return accountsSafe.getDeploymentAddress(who, nonce); + } + + function getDeploymentAddress(address who) internal view returns (address) { + return accountsSafe.getDeploymentAddress(who); + } + /// @dev Sets the specified `slot` in the storage of the given `self` address to the provided `value`. /// @param self The address to modify the storage of. /// @param slot The storage slot to set. diff --git a/test/_modules/Accounts.t.sol b/test/_modules/Accounts.t.sol index 93f19311..90eaa4d3 100644 --- a/test/_modules/Accounts.t.sol +++ b/test/_modules/Accounts.t.sol @@ -1,6 +1,6 @@ pragma solidity >=0.8.13 <0.9.0; -import {Test, expect, commands, accounts, console} from "../../src/test.sol"; +import {Test, expect, commands, accounts, console, ctx} from "../../src/test.sol"; import {Sender} from "../mocks/Sender.sol"; contract AccountsTest is Test { @@ -202,6 +202,32 @@ contract AccountsTest is Test { expect(token.balanceOf(user)).toEqual(balance - firstBurn - secondBurn); expect(token.totalSupply()).toEqual(totalSupply - firstBurn - secondBurn); } + + function testGetDeploymentAddressWithCurrentNonce(address user, uint64 nonce) external { + ctx.assume(nonce < type(uint64).max); + + address deploymentAddress = user.setNonce(uint64(nonce)).getDeploymentAddress(); + + user.impersonateOnce(); + + address deployedAddress = address(new TestToken()); + + expect(deployedAddress).toEqual(deploymentAddress); + } + + function testGetDeploymentAddress(address user, uint64 nonce) external { + ctx.assume(nonce < type(uint64).max); + + address deploymentAddress = user.getDeploymentAddress(nonce); + + user.setNonce(nonce); + + user.impersonateOnce(); + + address deployedAddress = address(new TestToken()); + + expect(deployedAddress).toEqual(deploymentAddress); + } } contract TestToken { From b55bf905cd8efd1d014b2490e9abdc474ef8b41d Mon Sep 17 00:00:00 2001 From: gnkz Date: Mon, 21 Aug 2023 14:19:18 -0400 Subject: [PATCH 17/17] docs: adds docs about `getDeploymentAddress` --- docs/src/reference/modules/accounts.md | 8 ++++++++ src/_modules/Accounts.sol | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/docs/src/reference/modules/accounts.md b/docs/src/reference/modules/accounts.md index 849c6cca..6c53cecc 100644 --- a/docs/src/reference/modules/accounts.md +++ b/docs/src/reference/modules/accounts.md @@ -48,6 +48,14 @@ Creates an address using the hash of the specified `name` as the private key and Creates an address using the hash of the specified `name` as the private key and adds a label to the address. +#### **`getDeploymentAddress(address who, uint64 nonce) → (address)`** + +Calculates the deployment address of `who` with nonce `nonce`. + +#### **`getDeploymentAddress(address who) → (address)`** + +Calculates the deployment address of `who` with the current nonce. + #### **`setStorage(address self, bytes32 slot, bytes32 value) → (address)`** Sets the specified `slot` in the storage of the given `self` address to the provided `value`. diff --git a/src/_modules/Accounts.sol b/src/_modules/Accounts.sol index 38c9a79b..bece367a 100644 --- a/src/_modules/Accounts.sol +++ b/src/_modules/Accounts.sol @@ -204,10 +204,15 @@ library accounts { return accountsSafe.create(name, lbl); } + /// @dev Calculates the deployment address of `who` with nonce `nonce`. + /// @param who The deployer address. + /// @param nonce The deployer nonce. function getDeploymentAddress(address who, uint64 nonce) internal pure returns (address) { return accountsSafe.getDeploymentAddress(who, nonce); } + /// @dev Calculates the deployment address of `who` with the current nonce. + /// @param who The deployer address. function getDeploymentAddress(address who) internal view returns (address) { return accountsSafe.getDeploymentAddress(who); }