Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ SSTORE2 should chunk saves to circumvent the 4M gas limit! #399

Open
RogerPodacter opened this issue May 2, 2023 · 3 comments
Open

Comments

@RogerPodacter
Copy link
Contributor

This gas limit is artificial and users shouldn't have to juggle it themselves. This is similar to automatically increasing the buffer when the user appends "too much" in DynamicBuffer. Many users are sheeple and cannot handle the complexity themselves!

Like with DynamicBuffer, SSTORE2 could operate with a custom struct that abstracts away these inessential details.

Something like:

struct StoredBytes {
    address[] locations;
}

using SSTORE2 for SSTORE2.StoredBytes;

function test(bytes memory input) public {
    StoredBytes memory blob = SSTORE2.coolWrite(input);
    
    blob.read();
}

Yes, this would mean that our children will store data on Ethereum without truly learning it goes into an address and how weird and cute that is, but I think it's worth it!

Here's something I found that handles the read side:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

/**
 * @title AddressChunks
 * @author @xtremetom
 * @notice Reads chunk pointers and merges their values
 */
library AddressChunks {
    function mergeChunks(address[] memory chunks)
        internal
        view
        returns (bytes memory o_code)
    {
        unchecked {
            assembly {
                let len := mload(chunks)
                let totalSize := 0x20
                let size := 0
                o_code := mload(0x40)

                // loop through all chunk addresses
                // - get address
                // - get data size
                // - get code and add to o_code
                // - update total size
                let targetChunk := 0
                for {
                    let i := 0
                } lt(i, len) {
                    i := add(i, 1)
                } {
                    targetChunk := mload(add(chunks, add(0x20, mul(i, 0x20))))
                    size := sub(extcodesize(targetChunk), 1)
                    extcodecopy(targetChunk, add(o_code, totalSize), 1, size)
                    totalSize := add(totalSize, size)
                }

                // update o_code size
                mstore(o_code, sub(totalSize, 0x20))
                // store o_code
                mstore(0x40, add(o_code, and(add(totalSize, 0x1f), not(0x1f))))
            }
        }
    }
}

Thoughts?

@0xClandestine
Copy link

Have been thinking about this myself, I'm a fan for sure 👏. While impractical due to gas, this could be used to store a somewhat high resolution image on-chain.

@RogerPodacter
Copy link
Contributor Author

Yes that's a good point, the client should probably do the chunking to save gas. So here is my revised proposal which I think is pretty good! Would be even better integrated into SSTORE2 of course.

library ChunkedSSTORE2Lib {
    struct ByteChunks {
        address[] locations;
    }
    
    function writeChunks(bytes[] memory chunks) internal returns (ByteChunks memory) {
        address[] memory locations = new address[](chunks.length);
        
        for (uint i; i < locations.length;) {
            locations[i] = SSTORE2.write(chunks[i]);
            unchecked {++i;}
        }
        
        return ChunkedSSTORE2Lib.ByteChunks(locations);
    }
    
    // AddressChunks by @xtremetom
    // https://github.com/intartnft/scripty.sol/blob/main/contracts/scripty/utils/AddressChunks.sol
    function readChunks(ByteChunks memory blob) internal view returns (bytes memory combinedBytes) {
        address[] memory chunks = blob.locations;
        
        unchecked {
            assembly {
                let len := mload(chunks)
                let totalSize := 0x20
                let size := 0
                combinedBytes := mload(0x40)

                // loop through all chunk addresses
                // - get address
                // - get data size
                // - get code and add to o_code
                // - update total size
                let targetChunk := 0
                for {
                    let i := 0
                } lt(i, len) {
                    i := add(i, 1)
                } {
                    targetChunk := mload(add(chunks, add(0x20, mul(i, 0x20))))
                    size := sub(extcodesize(targetChunk), 1)
                    extcodecopy(targetChunk, add(combinedBytes, totalSize), 1, size)
                    totalSize := add(totalSize, size)
                }

                // update o_code size
                mstore(combinedBytes, sub(totalSize, 0x20))
                // store o_code
                mstore(0x40, add(combinedBytes, and(add(totalSize, 0x1f), not(0x1f))))
            }
        }
    }
}

@RogerPodacter RogerPodacter changed the title Idea: SSTORE2 should chunk saves to circumvent the 4M gas limit! ✨ SSTORE2 should chunk saves to circumvent the 4M gas limit! May 3, 2023
@holic
Copy link

holic commented Jan 10, 2024

I am using a similar approach in EthFS, with the additional feature of allowing you to specify exactly the bytecode start/end for each address to support reusing bytecode from any arbitrary contract, not just ones stored via SSTORE2: https://github.com/holic/ethfs/blob/main/packages/contracts/src/File.sol

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

No branches or pull requests

3 participants