Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
2 changes: 2 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/"
]

fs_permissions = [{ access = "write", path = "./gas-reports/"}]

[fmt]
sort_imports = true
wrap_comments = true
Expand Down
74 changes: 74 additions & 0 deletions gas-reports/compare_gas_reports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import json

def load_json_file(filename):
try:
with open(filename, 'r') as f:
return json.load(f)
except FileNotFoundError:
print(f"Error: File '{filename}' not found")
return None
except json.JSONDecodeError:
print(f"Error: Invalid JSON in file '{filename}'")
return None

def compare_gas_usage(propose_batch_data, publish_data):
propose_batch_avg = propose_batch_data.get('average_gas_used_proposeBatch', 0)
publish_gas = publish_data.get('average_gas_used_publish', 0)

if propose_batch_avg == 0:
return {"error": "proposeBatch average gas is zero"}

if publish_gas == 0:
return {"error": "Publish average gas is zero"}

difference = propose_batch_avg - publish_gas

return {
"propose_batch_avg_gas": propose_batch_avg,
"publish_avg_gas": publish_gas,
"difference": difference,
}


def calculate_difference(value1, value2):
diff = value1 - value2
percentage_decrease = (diff / value1) * 100 if value1 != 0 else 0
return percentage_decrease


def main():
propose_batch_data = load_json_file('./gas-reports/propose_batch_gas_analysis.json')
publish_data = load_json_file('./gas-reports/minimal_inbox_publish.json')

results = compare_gas_usage(propose_batch_data, publish_data)
print(results)

propose_batch_avg_gas = results['propose_batch_avg_gas']
publish_avg_gas = results['publish_avg_gas']
difference = results['difference']
Comment on lines +43 to +48

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for comparison results.

The code assumes compare_gas_usage always returns valid comparison data, but it can return an error dictionary. This will cause a KeyError when trying to access keys like 'propose_batch_avg_gas'.

 results = compare_gas_usage(propose_batch_data, publish_data)
-print(results)
+
+if 'error' in results:
+    print(f"Error: {results['error']}")
+    return
 
 propose_batch_avg_gas = results['propose_batch_avg_gas']
 publish_avg_gas = results['publish_avg_gas']
 difference = results['difference']
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
results = compare_gas_usage(propose_batch_data, publish_data)
print(results)
propose_batch_avg_gas = results['propose_batch_avg_gas']
publish_avg_gas = results['publish_avg_gas']
difference = results['difference']
results = compare_gas_usage(propose_batch_data, publish_data)
if 'error' in results:
print(f"Error: {results['error']}")
return
propose_batch_avg_gas = results['propose_batch_avg_gas']
publish_avg_gas = results['publish_avg_gas']
difference = results['difference']
🤖 Prompt for AI Agents
In gas-reports/compare_gas_reports.py around lines 43 to 48, the code assumes
compare_gas_usage always returns valid data, but it may return an error
dictionary causing KeyError when accessing keys. Add error handling to check if
the results indicate an error before accessing keys like
'propose_batch_avg_gas'. If an error is present, handle it appropriately, such
as logging the error or raising an exception, to prevent KeyError.


print("Gas Usage Comparison Analysis")
print("=" * 50)
print(f"proposeBatch average gas used: {propose_batch_avg_gas:,}")
print(f"Publish average gas used: {publish_avg_gas:,}")
print(f"Difference: {difference:,}")

percent_decrease = calculate_difference(propose_batch_avg_gas, publish_avg_gas)

if percent_decrease > 0:
print(f"minimal_rollup_inbox_publish uses {percent_decrease:.2f}% LESS gas than alethia_inbox_propose")
elif percent_decrease < 0:
print(f"minimal_rollup_inbox_publish uses {abs(percent_decrease):.2f}% MORE gas than alethia_inbox_propose")
else:
print("Both methods use the same amount of gas")

results['percent_decrease'] = round(percent_decrease, 2)

with open("./gas-reports/gas_comparison_results.json", "w") as f:
json.dump(results, f, indent=2)
print("=" * 50)

print("\nResults saved to './gas-reports/gas_comparison_results.json'")

if __name__ == "__main__":
main()
6 changes: 6 additions & 0 deletions gas-reports/gas_comparison_results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"propose_batch_avg_gas": 181669,
"publish_avg_gas": 44563,
"difference": 137106,
"percent_decrease": 75.47
}
74 changes: 74 additions & 0 deletions gas-reports/get_taiko_gas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import requests
import math
import json
from typing import List, Dict, Any

# Taiko Inbox contract address
CONTRACT_ADDRESS = "0x06a9ab27c7e2255df1815e6cc0168d7755feb19a"

def fetch_transactions(contract_address: str, limit: int = 20) -> Dict[str, Any]:
url = f"https://api.tenderly.co/api/v1/public-contract/1/address/{contract_address}/explorer/transactions"
params = {"limit": limit}

try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except requests.RequestException as e:
print(f"Error fetching data from Tenderly API: {e}")
return {}

def extract_propose_batch_gas(transactions: List[Dict[str, Any]]) -> Dict[str, Any]:
sum_gas_used = 0
num_propose_batch = 0
min_gas_used = 0
max_gas_used = 0
for tx in transactions:
# make sure it calls propose batch AND its a blob txs
if tx.get("method") == "proposeBatch" and len(tx.get("blob_versioned_hashes")) != 0:
sum_gas_used += tx.get("gas_used")
num_propose_batch += 1
print(f"Transaction {tx.get("hash")} used {tx.get('gas_used')} gas")
if tx.get("gas_used") < min_gas_used or min_gas_used == 0:
min_gas_used = tx.get("gas_used")
if tx.get("gas_used") > max_gas_used or max_gas_used == 0:
max_gas_used = tx.get("gas_used")

average_gas_used = math.ceil(sum_gas_used / num_propose_batch)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix division by zero error.

If no proposeBatch transactions are found, num_propose_batch will be 0, causing a ZeroDivisionError when calculating the average.

+    if num_propose_batch == 0:
+        return {
+            "total_transactions": 0,
+            "average_gas_used_proposeBatch": 0,
+            "minimum_gas_used": 0,
+            "maximum_gas_used": 0
+        }
+
     average_gas_used = math.ceil(sum_gas_used / num_propose_batch)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
average_gas_used = math.ceil(sum_gas_used / num_propose_batch)
if num_propose_batch == 0:
return {
"total_transactions": 0,
"average_gas_used_proposeBatch": 0,
"minimum_gas_used": 0,
"maximum_gas_used": 0
}
average_gas_used = math.ceil(sum_gas_used / num_propose_batch)
🤖 Prompt for AI Agents
In gas-reports/get_taiko_gas.py at line 37, the calculation of average_gas_used
divides by num_propose_batch without checking if it is zero, which can cause a
ZeroDivisionError. Add a condition to check if num_propose_batch is greater than
zero before performing the division; if it is zero, set average_gas_used to zero
or handle it appropriately to avoid the error.


return {
"total_transactions": num_propose_batch,
"average_gas_used_proposeBatch": average_gas_used,
"minimum_gas_used": min_gas_used ,
"maximum_gas_used": max_gas_used
}


def main():
print(f"Fetching transactions for contract: {CONTRACT_ADDRESS}")
print("-" * 60)

response = fetch_transactions(CONTRACT_ADDRESS)

if response is None:
print("Failed to fetch transactions")
return
Comment on lines +53 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling for API response.

The fetch_transactions function returns an empty dict {} on error, not None. The current check won't catch API failures.

-    if response is None:
+    if not response:
         print("Failed to fetch transactions")
         return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if response is None:
print("Failed to fetch transactions")
return
if not response:
print("Failed to fetch transactions")
return
🤖 Prompt for AI Agents
In gas-reports/get_taiko_gas.py around lines 53 to 55, the code checks if
response is None to detect API failures, but fetch_transactions returns an empty
dict {} on error. Update the condition to check if the response is empty (e.g.,
if not response) instead of None to properly handle API failure cases.


transactions = response if isinstance(response, list) else response.get("data", [])

print(f"Total transactions fetched: {len(transactions)}")

propose_batch_txs_summary = extract_propose_batch_gas(transactions)

print(f"Found {propose_batch_txs_summary['total_transactions']} proposeBatch transactions")
print("-" * 60)

print(f"Average gas_used over {propose_batch_txs_summary["total_transactions"]} propose transactions: {propose_batch_txs_summary['average_gas_used_proposeBatch']:,}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix syntax error with quote usage.

The f-string contains conflicting quote usage that will cause a SyntaxError.

-    print(f"Average gas_used over {propose_batch_txs_summary["total_transactions"]} propose transactions: {propose_batch_txs_summary['average_gas_used_proposeBatch']:,}") 
+    print(f"Average gas_used over {propose_batch_txs_summary['total_transactions']} propose transactions: {propose_batch_txs_summary['average_gas_used_proposeBatch']:,}")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
print(f"Average gas_used over {propose_batch_txs_summary["total_transactions"]} propose transactions: {propose_batch_txs_summary['average_gas_used_proposeBatch']:,}")
print(f"Average gas_used over {propose_batch_txs_summary['total_transactions']} propose transactions: {propose_batch_txs_summary['average_gas_used_proposeBatch']:,}")
🤖 Prompt for AI Agents
In gas-reports/get_taiko_gas.py at line 66, the f-string uses conflicting double
quotes inside the string, causing a syntax error. Fix this by changing the inner
quotes around dictionary keys to single quotes or by escaping the inner double
quotes to ensure proper string delimitation.


with open("./gas-reports/propose_batch_gas_analysis.json", "w") as f:
json.dump(propose_batch_txs_summary, f, indent=2)

print("\nResults saved to 'propose_batch_gas_analysis.json'")

if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions gas-reports/minimal_inbox_publish.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{ "num_publications": 20, "average_gas_used_publish": 44563 }

6 changes: 6 additions & 0 deletions gas-reports/propose_batch_gas_analysis.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"total_transactions": 18,
"average_gas_used_proposeBatch": 181669,
"minimum_gas_used": 164334,
"maximum_gas_used": 182691
}
157 changes: 157 additions & 0 deletions gas-reports/report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Gas Optimisation Report: Taiko Inbox Implementation

## Summary

This report analyses the gas efficiency of Taiko's propose function (`proposeBatch`) using real on-chain data and compares it to the new implementation of the `publish` function in the minimal rollup inbox. This analysis serves as a preliminary benchmark, with more comprehensive testing planned once the minimal rollup can be deployed to a testnet.

## Methodology


### 1. Data Collection Approach

We adopted a hybrid approach combining real-world on-chain data with controlled testing:

**Taiko Data (Real On-chain):**

- Queried Taiko's deployed inbox contract (0x06a9ab27c7e2255df1815e6cc0168d7755feb19a) using Tenderly API
- Analysed the latest ~20 transactions to capture current gas usage patterns
- Filtered specifically for `proposeBatch` method calls
- Verified all transactions used blob storage (EIP-4844) for fair comparison


**Minimal Rollup Data (Test Environment):**

- Implemented a comparable test scenarios using Foundry


### 2. On-chain Data Analysis

Using the Tenderly API, we collected transaction data from Taiko's mainnet deployment:

```python
# API endpoint for Taiko Inbox contract
CONTRACT_ADDRESS = "0x06a9ab27c7e2255df1815e6cc0168d7755feb19a"
url = f"https://api.tenderly.co/api/v1/public-contract/1/address/{CONTRACT_ADDRESS}/explorer/transactions"

```

Key findings from the on-chain analysis:

- All transactions confirmed to use blob storage (`blob_versioned_hashes` present)

- Gas usage was consistent across transactions despite varying blob content


Sample transaction data:

```json
{
"method": "proposeBatch",
"gas_used": 164334,
"blob_versioned_hashes": ["0x013d43e92525b7a0d7c6d99937be8d55ed15e2860223ac58f5211e1475a94fbc"],
}
```


### 3. Test Implementation Details


#### Our Testing Setup

To provide a comparison point, we implemented tests that mirror typical rollup operations:


```solidity

uint256 numPublications = 20;

// Pre-populate publications to simulate active rollup state
ProposeMultiplePublications109)

// Measure 10 publications
uint256 numPublications = 20;
vm.startSnapshotGas("publish");
_publishMultiplePublications(numPublications);
uint256 publishGas = vm.stopSnapshotGas("publish");
uint256 gasPerPublication = publishGas / numPublications;
Comment thread
LeoPatOZ marked this conversation as resolved.
Comment on lines +66 to +74

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Replace stray call and align publication count

ProposeMultiplePublications(10) is undefined and lacks a semicolon, while the subsequent measurement block uses numPublications = 20 even though the comment says “Measure 10 publications”. This will confuse readers and could mislead anyone copy-pasting the snippet.

-// Pre-populate publications to simulate active rollup state
-ProposeMultiplePublications(10)
-
-// Measure 10 publications
-uint256 numPublications = 20;
+// Pre-populate publications to simulate active rollup state
+_publishMultiplePublications(10);
+
+// Measure 10 publications
+uint256 numPublications = 10;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Pre-populate publications to simulate active rollup state
ProposeMultiplePublications(10)
// Measure 10 publications
uint256 numPublications = 20;
vm.startSnapshotGas("publish");
_publishMultiplePublications(numPublications);
uint256 publishGas = vm.stopSnapshotGas("publish");
uint256 gasPerPublication = publishGas / numPublications;
// Pre-populate publications to simulate active rollup state
_publishMultiplePublications(10);
// Measure 10 publications
uint256 numPublications = 10;
vm.startSnapshotGas("publish");
_publishMultiplePublications(numPublications);
uint256 publishGas = vm.stopSnapshotGas("publish");
uint256 gasPerPublication = publishGas / numPublications;
🤖 Prompt for AI Agents
In gas-reports/report.md around lines 66 to 74, replace the undefined call
ProposeMultiplePublications(10) with a properly defined function call that
matches the context and add a missing semicolon. Also, align the numPublications
variable to 10 to match the comment "Measure 10 publications" for consistency
and clarity. Ensure the code snippet is syntactically correct and the comments
accurately reflect the code behavior.



function _publishMultiplePublications(uint256 numPublications) internal {
vm.roll(maxAnchorBlockIdOffset);
uint256 nBlobs = 1;
uint64 baseAnchorBlockId = uint64(block.number - 1);

bytes32[] memory blobHashes = new bytes32[](1);
blobHashes[0] = keccak256(abi.encodePacked("txList"));

vm.blobhashes(blobHashes);

for (uint256 i = 0; i < numPublications; i++) {
taikoInbox.publish(nBlobs, baseAnchorBlockId);
}
}

```


### 4. Data Processing

Python scripts were developed to:

1. Fetch and parse Taiko's on-chain transaction data
2. Filter for `proposeBatch` transactions
3. Calculate average gas consumption
4. Compare with our test results


## Results


### Gas Comparison


| Implementation | Gas per Operation | Data Source |
|---|---|---|
| Taiko `proposeBatch` (on-chain) | 181,669 | Real mainnet data (18 txs avg) |
| Our `publish` (test) | 44,563 | Foundry test environment (20 txs avg) |
| **Absolute Difference** | **137,106** |
| **Percentage Decrease** | **75.47%** |
Comment on lines +111 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix table formatting issues.

The table has inconsistent column counts. Lines 117-118 are missing cells for the third column, which will cause rendering issues.

| Implementation | Gas per Operation | Data Source |
|---|---|---|
| Taiko `proposeBatch` (on-chain) | 181,669 | Real mainnet data (18 txs avg) |
| Our `publish` (test) | 44,563 | Foundry test environment (20 txs avg) |
-|  **Absolute Difference**  |  **137,106**  |
-|  **Percentage Decrease**  |  **75.47%**  |
+|  **Absolute Difference**  |  **137,106**  | - |
+|  **Percentage Decrease**  |  **75.47%**  | - |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| Implementation | Gas per Operation | Data Source |
|---|---|---|
| Taiko `proposeBatch` (on-chain) | 181,669 | Real mainnet data (18 txs avg) |
| Our `publish` (test) | 44,563 | Foundry test environment (20 txs avg) |
| **Absolute Difference** | **137,106** |
| **Percentage Decrease** | **75.47%** |
| Implementation | Gas per Operation | Data Source |
|---|---|---|
| Taiko `proposeBatch` (on-chain) | 181,669 | Real mainnet data (18 txs avg) |
| Our `publish` (test) | 44,563 | Foundry test environment (20 txs avg) |
| **Absolute Difference** | **137,106** | - |
| **Percentage Decrease** | **75.47%** | - |
🧰 Tools
🪛 markdownlint-cli2 (0.17.2)

117-117: Table column count
Expected: 3; Actual: 2; Too few cells, row will be missing data

(MD056, table-column-count)


118-118: Table column count
Expected: 3; Actual: 2; Too few cells, row will be missing data

(MD056, table-column-count)

🤖 Prompt for AI Agents
In gas-reports/report.md around lines 113 to 118, the table rows for "Absolute
Difference" and "Percentage Decrease" have only two columns instead of three,
causing formatting issues. Add a third empty cell to these rows to maintain
consistent column counts across the table.



### On-chain Transaction Analysis

From the `proposeBatch` transactions analysed:

- Minimum gas used: 164,334
- Maximum gas used: 182,691
- Average gas used: 18,1669

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix typo in average gas usage.

There's a formatting error in the average gas used value.

-- Average gas used: 18,1669
+- Average gas used: 181,669
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Average gas used: 18,1669
- Average gas used: 181,669
🤖 Prompt for AI Agents
In gas-reports/report.md at line 127, the average gas used value contains a typo
with an incorrect comma placement. Correct the formatting by replacing the comma
with a period or removing it to properly represent the number as 181669 or
18.1669, depending on the intended value format.

- Standard deviation: ~18,357


It is worth noting this is somewhat consistent with taikos [gas analysis](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/gas-reports/inbox_without_provermarket.txt) based off foundry. Where the average cost of proposing was ~168,855


## Limitations

### Current Analysis Constraints

1. **Asymmetric Comparison Environment**
- Taiko data: Real on-chain transactions with actual network conditions
- Our data: Clinical test environment with idealised conditions using foundry

2. **Limited Sample Size**
- Only ~20 most recent transactions analysed
- Longer-term patterns and edge cases not captured

## Conclusion

The analysis shows a significant 75.47% gas reduction when using the minimal rollup's `publish` function compared to Taiko's `proposeBatch` based on real on-chain data.


## Next Steps

1. **Testnet Deployment**: Deploy minimal rollup contracts to a testnet for accurate comparison
2. **Extended Analysis**: Collect data over longer periods to capture various network conditions
3. **Load Testing**: Simulate various transaction volumes to identify scaling characteristics

Once the minimal rollup infrastructure is deployed to a testnet, we can conduct a more accurate comparison with both systems operating under identical network conditions.
3 changes: 3 additions & 0 deletions snapshots/TaikoInboxTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"publish": "891278"
}
Loading