Last active
August 28, 2023 22:07
-
-
Save emo-eth/5cfb69b97dbba70323856d15021800b1 to your computer and use it in GitHub Desktop.
Exploration in edge cases for account + storage warmness
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.17; | |
import {Test} from "forge-std/Test.sol"; | |
address constant callee = address(bytes20("call me maybe")); | |
interface Authorizer { | |
function allowed() external view returns (bool); | |
} | |
contract Thermometer { | |
error NotAllowed(); | |
uint256 public num; | |
constructor() { | |
num = 50; | |
callee.call(""); | |
if (!Authorizer(msg.sender).allowed()) { | |
revert NotAllowed(); | |
} | |
} | |
function writeNum(uint256 i) external { | |
num = i; | |
} | |
fallback(bytes calldata) external returns (bytes memory) {} | |
} | |
contract Deployer is Authorizer { | |
uint256 public _allowed; | |
constructor() { | |
_allowed = 1; | |
} | |
function deploy(bool allowed_) external returns (address result) { | |
if (allowed_) { | |
_allowed = 2; | |
} | |
bytes memory bytecode = type(Thermometer).creationCode; | |
assembly { | |
result := create2(0, add(bytecode, 0x20), mload(bytecode), 0) | |
} | |
_allowed = 1; | |
} | |
function allowed() external view override returns (bool) { | |
return _allowed == 2; | |
} | |
function setAllowed(uint256 allowed_) external { | |
_allowed = allowed_; | |
} | |
} | |
contract WarmTest is Test { | |
Thermometer immutable expl; | |
Deployer immutable deployer; | |
bool slotToWarm; | |
constructor() { | |
deployer = new Deployer(); | |
expl = Thermometer( | |
predictDeterministicAddress(address(deployer), bytes32(0), keccak256(type(Thermometer).creationCode)) | |
); | |
} | |
function predictDeterministicAddress(address sender, bytes32 salt, bytes32 initCodehash) | |
internal | |
pure | |
returns (address) | |
{ | |
return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), sender, salt, initCodehash))))); | |
} | |
function createContractSucceed() internal { | |
require(deployer.deploy(true) == address(expl), "succeed: deployed address mismatch"); | |
} | |
function createContractFail() internal { | |
Thermometer(deployer.deploy(false)); | |
} | |
function reportCallCost() internal { | |
uint256 startGas = gasleft(); | |
address(expl).call(""); | |
uint256 endGas = gasleft(); | |
emit log_named_uint("cost to call created contract", startGas - endGas); | |
} | |
function reportCallCalleeCost() internal { | |
uint256 startGas = gasleft(); | |
callee.call(""); | |
uint256 endGas = gasleft(); | |
emit log_named_uint("cost to call callee", startGas - endGas); | |
} | |
function reportStoreCost() internal { | |
uint256 startGas = gasleft(); | |
expl.writeNum(100); | |
uint256 endGas = gasleft(); | |
emit log_named_uint("cost to call + sstore non-empty slot in created contract", startGas - endGas); | |
} | |
function reportStoreLocalSlotCost() internal { | |
uint256 startGas = gasleft(); | |
slotToWarm = true; | |
uint256 endGas = gasleft(); | |
emit log_named_uint("cost to store local slot", startGas - endGas); | |
} | |
function reportDeployCost() internal { | |
uint256 startGas = gasleft(); | |
address result = deployer.deploy(true); | |
uint256 endGas = gasleft(); | |
emit log_named_uint("cost to deploy a contract that does a staticcall and an sstore", startGas - endGas); | |
require(result == address(expl), "deployed address mismatch"); | |
} | |
function testCallNoCreate() public { | |
reportCallCost(); | |
assertEq(address(expl).code.length, 0); | |
} | |
function testCallCreated() public { | |
createContractSucceed(); | |
reportCallCost(); | |
assertGt(address(expl).code.length, 0); | |
} | |
function testCallCalleeCreated() public { | |
createContractSucceed(); | |
reportCallCalleeCost(); | |
assertGt(address(expl).code.length, 0); | |
} | |
function testCallCalleeRevertedCreate() public { | |
createContractFail(); | |
reportCallCalleeCost(); | |
assertEq(address(expl).code.length, 0); | |
} | |
function testStoreCreated() public { | |
createContractSucceed(); | |
reportStoreCost(); | |
assertGt(address(expl).code.length, 0); | |
} | |
function testCallRevertedCreate() public { | |
createContractFail(); | |
reportCallCost(); | |
assertEq(address(expl).code.length, 0); | |
} | |
function testReportCreateCost() public { | |
// do an extra sstore to warm up the slot for better comparison to the after reverted case | |
deployer.setAllowed(1); | |
reportDeployCost(); | |
} | |
function testReportCreateAfterRevertedCost() public { | |
createContractFail(); | |
reportDeployCost(); | |
} | |
function testReportStoreLocalSlot() public { | |
slotToWarm = true; | |
reportStoreLocalSlotCost(); | |
} | |
function testReportStoreLocalSlotRevert() public { | |
try this.touchSlotRevert() {} catch Error(string memory) {} | |
reportStoreLocalSlotCost(); | |
} | |
function touchSlotRevert() external { | |
slotToWarm = true; | |
revert("revert"); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment