Skip to content

Add BytePackLib for efficient content storage#132

Merged
RogerPodacter merged 3 commits into
evm-backend-demofrom
byte_pack
Nov 4, 2025
Merged

Add BytePackLib for efficient content storage#132
RogerPodacter merged 3 commits into
evm-backend-demofrom
byte_pack

Conversation

@RogerPodacter

@RogerPodacter RogerPodacter commented Nov 4, 2025

Copy link
Copy Markdown
Member
  • Introduced BytePackLib to pack small byte arrays (0-31 bytes) into a bytes32 slot.
  • Updated Ethscriptions contract to use packed content or SSTORE2 pointers based on content size.
  • Modified content storage mapping to accommodate both packed data and SSTORE2 pointers.
  • Enhanced getEthscriptionContent function to handle unpacking of packed data.

Note

Introduce BytePackLib and switch content storage to a single bytes32 that inlines ≤31B data or stores an SSTORE2 pointer for larger payloads.

  • Core (Ethscriptions.sol):
    • Replace contentPointerBySha with unified contentStorageBySha: bytes32 storing either packed inline bytes (≤31) or an SSTORE2 pointer (≥32).
    • Add BytePackLib usage in getEthscriptionContent to detect/unpack inline data; otherwise read via SSTORE2Unlimited.
    • Update _storeContent to pack small calldata via BytePackLib.packCalldata or write to SSTORE2Unlimited and store pointer as bytes32.
  • Library:
    • Add BytePackLib with packCalldata, unpack, isPacked, and packedLength for 0–31 byte payloads.
  • Testing:
    • New BytePackLib.t.sol covering packing/unpacking, bounds, and format.
    • Add PackUnpackTest.t.sol examples for pack/unpack behavior.
    • Extend EthscriptionsWithTestFunctions to expose storage/read helpers compatible with packed-or-pointer format.
    • Update SSTORE2ContentSizes.t.sol to assert inline storage for <32B and pointers for ≥32B, and to validate retrieval/deduplication.

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

- Introduced BytePackLib to pack small byte arrays (0-31 bytes) into a bytes32 slot.
- Updated Ethscriptions contract to use packed content or SSTORE2 pointers based on content size.
- Modified content storage mapping to accommodate both packed data and SSTORE2 pointers.
- Enhanced getEthscriptionContent function to handle unpacking of packed data.
@RogerPodacter RogerPodacter requested a review from Copilot November 4, 2025 17:11

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 optimizes storage for small ethscription content by packing byte arrays up to 31 bytes directly into a single bytes32 storage slot, avoiding the gas cost of SSTORE2 deployments for small content. The implementation introduces a new BytePackLib library that uses a tag byte to distinguish packed data from SSTORE2 pointers.

  • Adds BytePackLib library for packing/unpacking small byte arrays (0-31 bytes) into bytes32
  • Refactors content storage to use packed format for content ≤31 bytes, SSTORE2 for ≥32 bytes
  • Updates contentPointerBySha mapping from address to bytes32 to support dual-mode storage

Reviewed Changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
contracts/src/libraries/BytePackLib.sol New library implementing byte packing/unpacking with tag-based format
contracts/src/Ethscriptions.sol Updated content storage to use packed format for small content or SSTORE2 pointers for larger content

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

Comment thread contracts/src/libraries/BytePackLib.sol Outdated
Comment on lines +54 to +55
// Packed data has a non-zero tag byte (1-32) in the first byte
return uint8(uint256(value >> 248)) != 0;

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.

The isPacked function only checks if the first byte is non-zero, which could lead to false positives. While Ethereum addresses (160 bits = 20 bytes) cast to bytes32 have their leftmost 12 bytes as zero under normal circumstances, the tag byte range (1-32) could theoretically overlap with the upper bits if an address somehow had data in those positions. Consider adding an additional check to validate that the tag byte is within the valid range (1-32) to make the distinction more robust: uint8 tag = uint8(uint256(value >> 248)); return tag > 0 && tag <= 32;

Suggested change
// Packed data has a non-zero tag byte (1-32) in the first byte
return uint8(uint256(value >> 248)) != 0;
// Packed data has a tag byte in the range 1-32 in the first byte
uint8 tag = uint8(uint256(value >> 248));
return tag > 0 && tag <= 32;

Copilot uses AI. Check for mistakes.
cursor[bot]

This comment was marked as outdated.

@RogerPodacter RogerPodacter requested a review from Copilot November 4, 2025 17:19

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

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


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

contentPointerBySha[contentSha] = SSTORE2Unlimited.write(content);
// Store based on size
if (content.length <= 31) {
// Pack small content directly into bytes32 (0-31 bytes)

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.

The condition content.length <= 31 is inconsistent with the BytePackLib implementation which supports packing data up to 31 bytes (if (len >= 32) revert). However, the comment on line 79 states for <32 bytes which is correct. The condition should use < instead of <= to match the boundary of 32 bytes mentioned in the comment, or the comment should be updated to say for <=31 bytes. The current code is correct but the comment inconsistency could cause confusion.

Suggested change
// Pack small content directly into bytes32 (0-31 bytes)
// Pack small content directly into bytes32 (for <=31 bytes)

Copilot uses AI. Check for mistakes.
// For large content (>=32 bytes), it's stored via SSTORE2 with a pointer
address pointer = eth.getContentPointer(txHash);
assertTrue(pointer != address(0), "Invalid content pointer");
if (content.length >= 32) {

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.

The boundary condition uses >= 32 to determine SSTORE2 storage, but line 516 in Ethscriptions.sol uses <= 31 for inline storage. While these are mathematically equivalent, they check opposite conditions. For consistency and clarity, this should use > 31 to match the conceptual model that content > 31 bytes uses SSTORE2, or both places should use the same boundary expression (>= 32 vs <= 31).

Suggested change
if (content.length >= 32) {
if (content.length > 31) {

Copilot uses AI. Check for mistakes.
address pointer = address(uint160(uint256(stored)));

// Verify it actually has code
if (pointer.code.length > 0) {

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.

[nitpick] The code check pointer.code.length > 0 may return false positives during contract construction. While unlikely in this test context since SSTORE2 contracts are immediately deployed, a more robust check would be pointer.code.length > 0 && pointer != address(0), though the prior address(0) return on line 37 makes this redundant. Consider simplifying by removing this check entirely since isPacked already filters out inline content.

Copilot uses AI. Check for mistakes.
@RogerPodacter RogerPodacter merged commit 9bcb6e3 into evm-backend-demo Nov 4, 2025
@RogerPodacter RogerPodacter deleted the byte_pack branch November 4, 2025 17:25
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