Skip to content

Instantly share code, notes, and snippets.

@emo-eth
Last active August 28, 2023 22:07
Show Gist options
  • Save emo-eth/5cfb69b97dbba70323856d15021800b1 to your computer and use it in GitHub Desktop.
Save emo-eth/5cfb69b97dbba70323856d15021800b1 to your computer and use it in GitHub Desktop.
Exploration in edge cases for account + storage warmness
// 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