Skip to content

Remove SSTORE2 chunking now that we removed the contract size cap in geth#131

Merged
RogerPodacter merged 1 commit into
evm-backend-demofrom
uncapped_sstore2
Nov 4, 2025
Merged

Remove SSTORE2 chunking now that we removed the contract size cap in geth#131
RogerPodacter merged 1 commit into
evm-backend-demofrom
uncapped_sstore2

Conversation

@RogerPodacter

@RogerPodacter RogerPodacter commented Nov 4, 2025

Copy link
Copy Markdown
Member

Note

Switches content storage to a single SSTORE2 pointer via SSTORE2Unlimited, removes chunking, and updates/expands tests to cover various sizes and deduplication.

  • Contracts:
    • Storage Simplification: Replace chunked storage with SSTORE2Unlimited.
      • Ethscriptions.sol: switch contentPointersBySha: bytes32 => address[] to contentPointerBySha: bytes32 => address; update read/write to use SSTORE2Unlimited.read/write.
      • Remove libraries/SSTORE2ChunkedStorageLib.sol; add libraries/SSTORE2Unlimited.sol (single-contract, unlimited-size storage).
  • Tests:
    • Update EthscriptionsWithTestFunctions.sol: remove chunk helpers; add hasContent, getContentPointer, readContent.
    • Rewrite EthscriptionsJson.t.sol to validate single-pointer storage and JSON outputs (no chunk assumptions); optimize loops.
    • Add SSTORE2ContentSizes.t.sol: comprehensive size tests (empty → 2MB), retrieval, and deduplication gas checks.
  • Config:
    • foundry.toml: set gas_limit to 18446744073709551615.

Written by Cursor Bugbot for commit 356f2c7. This will update automatically on new commits. Configure here.

@RogerPodacter RogerPodacter requested a review from Copilot November 4, 2025 16:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the content storage mechanism from a chunked SSTORE2 approach to a single-contract SSTORE2Unlimited implementation that can handle arbitrarily large content (up to 4GB).

Key changes:

  • Replaces the SSTORE2ChunkedStorageLib with a new SSTORE2Unlimited library that stores all content in a single contract deployment
  • Simplifies the storage model from mapping(bytes32 => address[]) to mapping(bytes32 => address) for content pointers
  • Adds comprehensive test suite for various content sizes from 0 bytes to 2MB

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
contracts/src/libraries/SSTORE2Unlimited.sol New library implementing unlimited-size SSTORE2 storage using a single contract deployment
contracts/src/libraries/SSTORE2ChunkedStorageLib.sol Removed old chunked storage library
contracts/src/Ethscriptions.sol Updated to use single pointer instead of array of pointers for content storage
contracts/test/EthscriptionsWithTestFunctions.sol Updated test helper functions from chunk-based to single-pointer model
contracts/test/SSTORE2ContentSizes.t.sol New comprehensive test suite for various content sizes
contracts/test/EthscriptionsJson.t.sol Updated tests from chunk verification to single-pointer verification; improved loop efficiency
contracts/foundry.toml Increased gas limit to maximum value for testing large content

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

address[] storage pointers = contentPointersBySha[ethscription.contentSha];
// Empty content is valid - returns "" for empty pointers array
return pointers.read();
address pointer = contentPointerBySha[ethscription.contentSha];

Copilot AI Nov 4, 2025

Copy link

Choose a reason for hiding this comment

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

Potential issue when reading content with a zero address pointer. If contentPointerBySha[ethscription.contentSha] is address(0) (which can happen for empty content or if content was never stored), calling SSTORE2Unlimited.read(pointer) will attempt to read from address(0). In the read function at line 69, this will compute pointer.code.length - DATA_OFFSET which for address(0) becomes 0 - 1, causing an underflow. Consider adding a check: if (pointer == address(0)) return new bytes(0); before the read call.

Suggested change
address pointer = contentPointerBySha[ethscription.contentSha];
address pointer = contentPointerBySha[ethscription.contentSha];
if (pointer == address(0)) {
return new bytes(0);
}

Copilot uses AI. Check for mistakes.
Comment on lines +169 to +174
assertEq(retrievedContent[0], originalContent[0], "First byte mismatch");
assertEq(
retrievedContent[retrievedContent.length - 1],
originalContent[originalContent.length - 1],
"Last byte mismatch"
);

Copilot AI Nov 4, 2025

Copy link

Choose a reason for hiding this comment

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

Potential array out-of-bounds access for empty content. The _verifyContent function is called when size > 0 (line 142), but it doesn't guard against empty arrays. If an empty content somehow reaches this function, accessing index 0 or length - 1 will revert. While the caller checks size > 0, adding a size check at the start of this function would make it more defensive.

Copilot uses AI. Check for mistakes.
Comment on lines +178 to +179
for (uint i = 0; i < size; i++) {
assertEq(retrievedContent[i], originalContent[i], "Byte mismatch");

Copilot AI Nov 4, 2025

Copy link

Choose a reason for hiding this comment

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

Missing unchecked block for loop increment. Since i is bounded by size which is at most 1024 in this branch, and the loop is already incrementing with i++, wrapping the increment in an unchecked block would save gas and be consistent with the pattern used elsewhere in the codebase (e.g., lines 217-219, 437-439).

Suggested change
for (uint i = 0; i < size; i++) {
assertEq(retrievedContent[i], originalContent[i], "Byte mismatch");
for (uint i = 0; i < size;) {
assertEq(retrievedContent[i], originalContent[i], "Byte mismatch");
unchecked { ++i; }

Copilot uses AI. Check for mistakes.
Comment on lines +236 to +237
for (uint256 i = 0; i < sizes.length; i++) {
_testDeduplication(sizes[i], string.concat("Size: ", vm.toString(sizes[i])));

Copilot AI Nov 4, 2025

Copy link

Choose a reason for hiding this comment

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

Missing unchecked block for loop increment. The loop counter i is bounded by the fixed-size array length (5 elements), so overflow is impossible. Wrapping the increment in an unchecked block would save gas and be consistent with the pattern used elsewhere in the file (e.g., lines 217-219).

Suggested change
for (uint256 i = 0; i < sizes.length; i++) {
_testDeduplication(sizes[i], string.concat("Size: ", vm.toString(sizes[i])));
for (uint256 i = 0; i < sizes.length;) {
_testDeduplication(sizes[i], string.concat("Size: ", vm.toString(sizes[i])));
unchecked { ++i; }

Copilot uses AI. Check for mistakes.
// Empty content is valid - returns "" for empty pointers array
return pointers.read();
address pointer = contentPointerBySha[ethscription.contentSha];
return SSTORE2Unlimited.read(pointer);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Overflow on zero address in SSTORE2Unlimited.read breaking empty content handling

The SSTORE2Unlimited.read function underflows when called with address(0), as pointer.code.length - DATA_OFFSET becomes 0 - 1. This causes getEthscriptionContent to revert for existing ethscriptions with empty content, because contentPointerBySha returns address(0) for these. This breaks backward compatibility, as the old system gracefully returned empty bytes for such cases.

Additional Locations (1)

Fix in Cursor Fix in Web

@RogerPodacter RogerPodacter merged commit 51cc758 into evm-backend-demo Nov 4, 2025
8 checks passed
@RogerPodacter RogerPodacter deleted the uncapped_sstore2 branch November 4, 2025 16:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants