Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
[submodule "apps/openzeppelin-upgrades/lib/openzeppelin-foundry-upgrades"]
path = apps/openzeppelin-upgrades/lib/openzeppelin-foundry-upgrades
url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades
[submodule "apps/eip-2935-gas-comparison/lib/forge-std"]
path = apps/eip-2935-gas-comparison/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "apps/eip-7702-gas-sponsorship/lib/forge-std"]
path = apps/eip-7702-gas-sponsorship/lib/forge-std
url = https://github.com/foundry-rs/forge-std
Expand Down
17 changes: 17 additions & 0 deletions apps/eip-2935-gas-comparison/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
BEPOLIA_RPC_URL=https://bepolia.rpc.berachain.com/
TEST_RPC_URL=http://localhost:8545
BERASCAN_API_KEY=
LOCAL_HOST=http://localhost:8545
ETH_RPC_URL=
ETH_CHAIN_ID=1
BLOCK_NUMBER=5045482

# YOUR OWN WALLET DETAILS FOR DEPLOYING TO ACTUAL NETWORKS
EOA_WALLET1_ADDRESS=
EOA_PRIVATE_KEY=

# EOA AND SPONSOR ADDRESSES ARE ANVIL WALLET ADDRESSES 1 AND 2, RESPECTIVELY FOR TESTING
ANVIL_WALLET_1=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
ANVIL_WALLET_1_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
ANVIL_WALLET_2=0x70997970C51812dc3A010C7d01b50e0d17dc79C8
ANVIL_WALLET_2_PRIVATE_KEY=0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
14 changes: 14 additions & 0 deletions apps/eip-2935-gas-comparison/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/
.github

# Ignores development broadcast logs
!/broadcast
/broadcast/*

# Docs
docs/

# Dotenv file
.env
273 changes: 273 additions & 0 deletions apps/eip-2935-gas-comparison/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
# EIP-2935 Gas-Efficient Blockhash Access for Berachain Developers

This quickstart covers [EIP-2935, _an EIP focused on historical block hashes from state_,](https://eips.ethereum.org/EIPS/eip-2935) and how it enables gas optimizations for applications building on Berachain. It is part of the [Bectra upgrade](https://x.com/berachain/status/1930326162577776655), which brings Ethereum’s Pectra-era EIPs to Berachain.

A more detailed version of this guide, with context on EIP-2935, can be found within our [docs](https://docs.berachain.com/developers/).
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

we should have the real link. this is a bit weird to me since the docs should just be explaining vs. guide so just more for more information on eip 2935 maybe

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My understanding was it was the opposite:

  • docs would have the detailed explanation, including EIP2935 context etc.
  • guide was a quickstart with the code and everything.

This was based on what Manny had requested from me for EIP7702, but maybe I misinterpreted, or it really should be the other way around.

As for the link, I can't update it until the docs PR is merged and vice versa. Then I'd have the proper docs URL to put in. I guess I can put in what it should be ahead of time.


## Quick Context

EIP-2935 introduces a system contract that stores the last 8,191 block hashes in a ring buffer, making them readily available onchain. This dramatically improves the developer experience for use cases that rely on historical blockhashes, without requiring manual storage or trusted offchain sources.

This guide specifically shows obtaining a historic blockhash using the power of EIP-2935 and its system contracts, all on Bepolia. This can be done on Berachain as well.

This guide primarily revolves around the following files:

- `eip2935GasComparison.sol` - A simple implementation showcasing the methods for obtaining a blockhash, including storing them pre-EIP-2935.
- `eip2935GasComparison.t.sol` - A simple test suite to showcase unit testing with the `eip2935GasComparison.sol` contract.
- `eip2935GasComparison.s.sol` - A solidity script used to deploy the `eip2935GasComparison.sol` and make calls to it to simulate different blockhash reading methods.
- `run_gas_comparison.sh` - A bash script created to deploy `eip2935GasComparison.sol` and tabulate the gas expenditure results.

### Requirements

Make sure you have the following installed on your computer before we begin:

- Foundry (Foirge & Cast): v1.0.0 or greater
- Solidity: version ^0.8.29

If you have not installed these before, simply follow the commands below:

```bash
curl -L https://foundry.paradigm.xyz | bash
foundryup # Ensures you have the latest version (with solc auto-install support)
```

If you've already installed Foundry, just run:

```bash
foundryup
```

> This guide requires Solidity ^0.8.29. `forge build` will automatically download the right version if you're using a modern `forge` via `foundryup`.

Next, go through the following steps to setup your dependencies and `.env`.

### Step 1 - Install Dependencies


```bash
cd apps/eip-2935-gas-comparison
```

```bash
# FROM ./
pnpm install && cp .env.example .env
```

> ℹ️ forge install pulls in required dependencies like forge-std. Don’t skip it.

```bash
# FROM ./
forge install && forge build
```

### Step 2 - Run Forge Tests

The majority of this guide will go through running tests against an anvil fork. Of course, running unit tests is important for the development cycle in the beginning. We have provided quick foundry tests to showcase checks that the example implementation contract, `eip2935GasComparison.sol`, is functioning properly before testing against anvil forks or on actual networks.

> NOTE: The tests in this guide are setup to test against a Bepolia fork-url because the typical foundry EVM environment does not reflect EIP-2935 (and the needed system contract), and the other Bectra upgrades.

Run tests by running:

```bash-vue
# FROM: ./
source .env && forge test --fork-url $BEPOLIA_RPC_URL --fork-block-number 5045482
```

You should see an ouput showcasing the tests passing:

```bash
[⠊] Compiling...
[⠔] Compiling 1 files with Solc 0.8.29
[⠒] Solc 0.8.29 finished in 426.77ms
Compiler run successful!

Ran 4 tests for test/eip2935GasComparison.t.sol:GasComparisonTest
[PASS] testBlock() (gas: 2515)
[PASS] testGas_OracleSubmission() (gas: 39693)
[PASS] testGas_ReadWithGet() (gas: 41618)
[PASS] testGas_ReadWithSLOAD() (gas: 59398)
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.05ms (679.63µs CPU time)

Ran 1 test suite in 278.65ms (1.05ms CPU time): 4 tests passed, 0 failed, 0 skipped (4 total tests)
```

The pertinent tests to ensure that `eip2935GasComparison.sol` implementation is functioning properly includes:

- `testGas_OracleSubmission()`: Checking that the oracle-based pattern methods for obtaining blockhash history and inherent historic data functions
- `testGas_ReadWithGet()`: Checking the usage of the system contract as per EIP2935 for obtaining historic blockhashes
- `testGas_ReadWithSLOAD()`: Checking that the SSTORE and SLOAD pattern methods for storing and obtaining blockhash history, respectively, functions properly

Now we can move onto testing with an actual script either against an anvil network or an actual network.

### Step 3 - Start Your Anvil Fork

Run the following command to deploy a local anvil fork via your terminal. You need to specify the block number shown below to ensure that the EIP2935 system contract will function properly to reflect being activated after Bectra upgrades on Bepolia.

```bash-vue
# FROM: ./
source .env && anvil --fork-url $BEPOLIA_RPC_URL --fork-block-number 4867668 --chain-id 80069 --port 8545
```

### Step 4 - Update Your `.env` and Deploy `eip2935GasComparison.sol` Implementation

Now that you have an Anvil fork running, open up another terminal window and continue. Next you'll run a script on the local bepolia Anvil fork.

Update your `.env` with your `EOA_PRIVATE_KEY` and make sure it has enough $tBERA for deployment. A single $tBERA should be more than enough.

```bash
# FROM: ./

./script/run_gas_comparison.sh

```

#### Expected Similar Results:

After running `run_gas_comparison.sh` you should see results like the following in your terminal:

```bash
./script/run_gas_comparison.sh
🔧 Running eip2935GasComparison.s.sol script...
No files changed, compilation skipped
Warning: EIP-3855 is not supported in one or more of the RPCs used.
Unsupported Chain IDs: 80069.
Contracts deployed with a Solidity version equal or higher than 0.8.20 might not work properly
. For more information, please see https://eips.ethereum.org/EIPS/eip-3855
Traces:
[507107] eip2935GasComparison::run()
├─ [0] VM::startBroadcast()
│ └─ ← [Return]
├─ [412864] → new BlockhashConsumer@0x59ef61D43bdAF8B1257071a2035Ef5789f46463f
│ └─ ← [Return] 2062 bytes of code
├─ [0] console::log("Consumer contract deployed at: %s", BlockhashConsumer: [0x59ef61D43bd
AF8B1257071a2035Ef5789f46463f]) [staticcall] │ └─ ← [Stop]
├─ [0] console::log("Current block: %s", 4867668 [4.867e6]) [staticcall]
│ └─ ← [Stop]
├─ [22677] BlockhashConsumer::storeWithSSTORE(4867666 [4.867e6])
│ └─ ← [Stop]
├─ [6497] BlockhashConsumer::readWithGet(4867666 [4.867e6]) [staticcall]
│ ├─ [2225] 0x0000F90827F1C53a10cb7A02335B175320002935::00000000(00000000000000000000000
0000000000000000000000000004a4652) [staticcall] │ │ └─ ← [Return] 0x713825db1a93b11015ba43eb0eea7005c55c7b98375dda1961cc9c3c96d03c0b
│ └─ ← [Return] 0x713825db1a93b11015ba43eb0eea7005c55c7b98375dda1961cc9c3c96d03c0b
├─ [22784] BlockhashConsumer::submitOracleBlockhash(4867666 [4.867e6], 0x713825db1a93b1101
5ba43eb0eea7005c55c7b98375dda1961cc9c3c96d03c0b) │ └─ ← [Stop]
├─ [0] VM::stopBroadcast()
│ └─ ← [Return]
└─ ← [Stop]


Script ran successfully.

== Logs ==
Consumer contract deployed at: 0x59ef61D43bdAF8B1257071a2035Ef5789f46463f
Current block: 4867668

## Setting up 1 EVM.
==========================
Simulated On-chain Traces:

[412864] → new BlockhashConsumer@0x59ef61D43bdAF8B1257071a2035Ef5789f46463f
└─ ← [Return] 2062 bytes of code

[22677] BlockhashConsumer::storeWithSSTORE(4867666 [4.867e6])
└─ ← [Stop]

[22784] BlockhashConsumer::submitOracleBlockhash(4867666 [4.867e6], 0x713825db1a93b11015ba43
eb0eea7005c55c7b98375dda1961cc9c3c96d03c0b) └─ ← [Stop]


==========================

Chain 80069

Estimated gas price: 20.000000014 gwei

Estimated total gas used for script: 771474

Estimated amount required: 0.015429480010800636 BERA

==========================

##### berachain-bepolia
✅ [Success] Hash: 0x04112af6eee2ec29d1647353a2854e803d360856db00f7e65267bd5640958daa
Contract Address: 0x59ef61D43bdAF8B1257071a2035Ef5789f46463f
Block: 4867669
Paid: 0.009934880003477208 ETH (496744 gas * 20.000000007 gwei)


##### berachain-bepolia
✅ [Success] Hash: 0x08e8aa80c9e97fef47566f56215969536631228ba0f37fecd54bbb44fe5a6bd1
Block: 4867670
Paid: 0.000878100000307335 ETH (43905 gas * 20.000000007 gwei)


##### berachain-bepolia
✅ [Success] Hash: 0xa098ac84c403a04265ba3657a177382957547201eea8360564335e0a6b5fe4af
Block: 4867670
Paid: 0.000890480000311668 ETH (44524 gas * 20.000000007 gwei)

✅ Sequence #1 on berachain-bepolia | Total Paid: 0.011703460004096211 ETH (585173 gas * avg 2
0.000000007 gwei)

==========================

ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.

Transactions saved to: ichiraku/guides/apps/eip-
2935-gas-comparison/broadcast/eip2935GasComparison.s.sol/80069/run-latest.json
Sensitive values saved to: ichiraku/guides/apps/
eip-2935-gas-comparison/cache/eip2935GasComparison.s.sol/80069/run-latest.json
✅ Script execution complete. Parsing gas usage...

📄 Table saved to gas_comparison.md


# EIP-2935 Gas Comparison

| Pattern | Methods Involved | Total Gas |
|-------------------------------------|------------------------------------------|-----------|
| Before EIP-2935: SSTORE pattern | storeWithSSTORE(...) | 45354 |
| After EIP-2935: .get() access | readWithGet(...) | 6497 |
| Before EIP-2935: Oracle pattern | submitOracleBlockhash(...) | 45568 |

```

### Step 5 - Understanding What the Script Does

The bash script, `run_gas_comparison.sh` deploys the `eip2935GasComparison.sol` contract on the locally ran anvil fork of Bepolia. It then goes through the results and tabulates the total gas expenses for each blockhashing method, including storing the blockhash or replicating the usage of an oracle.

### Step 6 - Highlevel Review of the Solidity File

This project demonstrates and benchmarks different blockhash access patterns:

1. Manual SSTORE of blockhash (pre-EIP-2935 workaround) and direct SLOAD readback of stored hash
2. EIP-2935-style .get() call to a mock system contract
3. Oracle-submitted blockhash pattern simulating offchain access

You can see the details of the code in `eip2935GasComparison.sol`.

> It is very important to note that the system contract only receives the `calldata`, and there is no specification of the function signature or anything. See the explaination below. You can see this more within the `eip2935GasComparison.sol` `readWithGet()` function.

### Step 7 - Assessing the Results

The table is output in `gas_comparison.md` at the root of this subdirectory, where we can see the gas savings when comparing one method to the next.

Below is an example output that you ought to see when running the bash script:

| Pattern | Methods Involved | Total Gas |
| ------------------------------- | ----------------------------------------------- | --------- |
| Before EIP-2935: SSTORE pattern | storeWithSSTORE(...), readWithSLOAD(...) | 46210 |
| After EIP-2935: .get() access | readWithGet(...) | 6494 |
| Before EIP-2935: Oracle pattern | submitOracleBlockhash(...), readFromOracle(...) | 46338 |

Simply reading the blockhash from the system contract resulted in significantly less gas expenditure compared to the other methods typically used before EIP-2935. A table showcasing the savings can be seen below when comparing against the new method of just reading from the system contract.

| Pattern | Total Gas | Savings vs. `.get()` | % Saved Compared to `.get()` |
| ------------------------------- | --------- | -------------------- | ---------------------------- |
| Before EIP-2935: SSTORE pattern | 46,210 | 39,716 | 85.95% |
| Before EIP-2935: Oracle pattern | 46,338 | 39,844 | 86.00% |
| After EIP-2935: .get() access | 6,494 | — | — |

> It should be noted that if carrying out any of these calls cold results in the gas expenditures above, and warm calls will be less. The comparative analysis still stands even with this in mind.

🐻🎉 Congrats! You have finished the guide and have now seen the gas savings that come about with EIP-2935 when accessing historical blockhashes.
6 changes: 6 additions & 0 deletions apps/eip-2935-gas-comparison/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
7 changes: 7 additions & 0 deletions apps/eip-2935-gas-comparison/gas_comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# EIP-2935 Gas Comparison

| Pattern | Methods Involved | Total Gas |
| ------------------------------- | -------------------------- | --------- |
| Before EIP-2935: SSTORE pattern | storeWithSSTORE(...) | 45354 |
| After EIP-2935: .get() access | readWithGet(...) | 6497 |
| Before EIP-2935: Oracle pattern | submitOracleBlockhash(...) | 45568 |
1 change: 1 addition & 0 deletions apps/eip-2935-gas-comparison/lib/forge-std
Submodule forge-std added at 77041d
1 change: 1 addition & 0 deletions apps/eip-2935-gas-comparison/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
forge-std/=lib/forge-std/src/
31 changes: 31 additions & 0 deletions apps/eip-2935-gas-comparison/script/eip2935GasComparison.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {Script, console2} from "forge-std/Script.sol";
import {BlockhashConsumer} from "../src/Eip2935GasComparison.sol";

/// @dev This is to be ran from `./run_gas_comparison.sh` but you can run it separately from this file by using: `source .env && forge script script/eip2935GasComparison.s.sol:eip2935GasComparison.s --rpc-url $TEST_RPC_URL --private-key $EOA_PRIVATE_KEY --broadcast -vvvv`
contract eip2935GasComparison is Script {
function run() external {
vm.startBroadcast();

BlockhashConsumer consumer = new BlockhashConsumer();

console2.log("Consumer contract deployed at: %s", address(consumer));
console2.log("Current block: %s", block.number);

uint256 testBlock = block.number >= 2 ? block.number - 2 : 0;
bytes32 hash = blockhash(testBlock);

// Simulate Pre-EIP-2935 pattern where they manually store blockhash for future use
consumer.storeWithSSTORE(testBlock);

// Use system contract as per EIP-2935
consumer.readWithGet(testBlock);

// Simulate Pre-EIP-2935 pattern using oracles
consumer.submitOracleBlockhash(testBlock, hash);

vm.stopBroadcast();
}
}
Loading
Loading