// SPDX-License-Identifier: MIT // Copyright (c) 2021 the ethier authors (github.com/divergencetech/ethier) pragma solidity ^0.8.22; /// @title DynamicBuffer /// @author David Huber (@cxkoda) and Simon Fremaux (@dievardump). See also /// https://raw.githubusercontent.com/dievardump/solidity-dynamic-buffer /// @notice This library is used to allocate a big amount of container memory // which will be subsequently filled without needing to reallocate /// memory. /// @dev First, allocate memory. /// Then use `buffer.appendUnchecked(theBytes)` or `appendSafe()` if /// bounds checking is required. library DynamicBuffer { /// @notice Allocates container space for the DynamicBuffer /// @param capacity_ The intended max amount of bytes in the buffer /// @return buffer The memory location of the buffer /// @dev Allocates `capacity_ + 0x60` bytes of space /// The buffer array starts at the first container data position, /// (i.e. `buffer = container + 0x20`) function allocate(uint256 capacity_) internal pure returns (bytes memory buffer) { assembly { // Get next-free memory address let container := mload(0x40) // Allocate memory by setting a new next-free address { // Add 2 x 32 bytes in size for the two length fields // Add 32 bytes safety space for 32B chunked copy let size := add(capacity_, 0x60) let newNextFree := add(container, size) mstore(0x40, newNextFree) } // Set the correct container length { let length := add(capacity_, 0x40) mstore(container, length) } // The buffer starts at idx 1 in the container (0 is length) buffer := add(container, 0x20) // Init content with length 0 mstore(buffer, 0) } return buffer; } /// @notice Appends data to buffer, and update buffer length /// @param buffer the buffer to append the data to /// @param data the data to append /// @dev Does not perform out-of-bound checks (container capacity) /// for efficiency. function appendUnchecked(bytes memory buffer, bytes memory data) internal pure { assembly { let length := mload(data) for { data := add(data, 0x20) let dataEnd := add(data, length) let copyTo := add(buffer, add(mload(buffer), 0x20)) } lt(data, dataEnd) { data := add(data, 0x20) copyTo := add(copyTo, 0x20) } { // Copy 32B chunks from data to buffer. // This may read over data array boundaries and copy invalid // bytes, which doesn't matter in the end since we will // later set the correct buffer length, and have allocated an // additional word to avoid buffer overflow. mstore(copyTo, mload(data)) } // Update buffer length mstore(buffer, add(mload(buffer), length)) } } /// @notice Appends data to buffer, and update buffer length /// @param buffer the buffer to append the data to /// @param data the data to append /// @dev Performs out-of-bound checks and calls `appendUnchecked`. function appendSafe(bytes memory buffer, bytes memory data) internal pure { checkOverflow(buffer, data.length); appendUnchecked(buffer, data); } /// @notice Appends data encoded as Base64 to buffer. /// @param fileSafe Whether to replace '+' with '-' and '/' with '_'. /// @param noPadding Whether to strip away the padding. /// @dev Encodes `data` using the base64 encoding described in RFC 4648. /// See: https://datatracker.ietf.org/doc/html/rfc4648 /// Author: Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol) /// Author: Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol) /// Author: Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos. function appendSafeBase64( bytes memory buffer, bytes memory data, bool fileSafe, bool noPadding ) internal pure { uint256 dataLength = data.length; if (data.length == 0) { return; } uint256 encodedLength; uint256 r; assembly { // For each 3 bytes block, we will have 4 bytes in the base64 // encoding: `encodedLength = 4 * divCeil(dataLength, 3)`. // The `shl(2, ...)` is equivalent to multiplying by 4. encodedLength := shl(2, div(add(dataLength, 2), 3)) r := mod(dataLength, 3) if noPadding { // if r == 0 => no modification // if r == 1 => encodedLength -= 2 // if r == 2 => encodedLength -= 1 encodedLength := sub( encodedLength, add(iszero(iszero(r)), eq(r, 1)) ) } } checkOverflow(buffer, encodedLength); assembly { let nextFree := mload(0x40) // Store the table into the scratch space. // Offsetted by -1 byte so that the `mload` will load the character. // We will rewrite the free memory pointer at `0x40` later with // the allocated size. mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef") mstore( 0x3f, sub( "ghijklmnopqrstuvwxyz0123456789-_", // The magic constant 0x0230 will translate "-_" + "+/". mul(iszero(fileSafe), 0x0230) ) ) // Skip the first slot, which stores the length. let ptr := add(add(buffer, 0x20), mload(buffer)) let end := add(data, dataLength) // Run over the input, 3 bytes at a time. // prettier-ignore // solhint-disable-next-line no-empty-blocks for {} 1 {} { data := add(data, 3) // Advance 3 bytes. let input := mload(data) // Write 4 bytes. Optimized for fewer stack operations. mstore8( ptr , mload(and(shr(18, input), 0x3F))) mstore8(add(ptr, 1), mload(and(shr(12, input), 0x3F))) mstore8(add(ptr, 2), mload(and(shr( 6, input), 0x3F))) mstore8(add(ptr, 3), mload(and( input , 0x3F))) ptr := add(ptr, 4) // Advance 4 bytes. // prettier-ignore if iszero(lt(data, end)) { break } } if iszero(noPadding) { // Offset `ptr` and pad with '='. We can simply write over the end. mstore8(sub(ptr, iszero(iszero(r))), 0x3d) // Pad at `ptr - 1` if `r > 0`. mstore8(sub(ptr, shl(1, eq(r, 1))), 0x3d) // Pad at `ptr - 2` if `r == 1`. } mstore(buffer, add(mload(buffer), encodedLength)) mstore(0x40, nextFree) } } /// @notice Appends data encoded as Base64 to buffer. /// @param fileSafe Whether to replace '+' with '-' and '/' with '_'. /// @param noPadding Whether to strip away the padding. /// @dev Encodes `data` using the base64 encoding described in RFC 4648. /// See: https://datatracker.ietf.org/doc/html/rfc4648 /// Author: Modified from Solady (https://github.com/vectorized/solady/blob/main/src/utils/Base64.sol) /// Author: Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/Base64.sol) /// Author: Modified from (https://github.com/Brechtpd/base64/blob/main/base64.sol) by Brecht Devos. function appendUncheckedBase64( bytes memory buffer, bytes memory data, bool fileSafe, bool noPadding ) internal pure { uint256 dataLength = data.length; if (data.length == 0) { return; } uint256 encodedLength; uint256 r; assembly { // For each 3 bytes block, we will have 4 bytes in the base64 // encoding: `encodedLength = 4 * divCeil(dataLength, 3)`. // The `shl(2, ...)` is equivalent to multiplying by 4. encodedLength := shl(2, div(add(dataLength, 2), 3)) r := mod(dataLength, 3) if noPadding { // if r == 0 => no modification // if r == 1 => encodedLength -= 2 // if r == 2 => encodedLength -= 1 encodedLength := sub( encodedLength, add(iszero(iszero(r)), eq(r, 1)) ) } } assembly { let nextFree := mload(0x40) // Store the table into the scratch space. // Offsetted by -1 byte so that the `mload` will load the character. // We will rewrite the free memory pointer at `0x40` later with // the allocated size. mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef") mstore( 0x3f, sub( "ghijklmnopqrstuvwxyz0123456789-_", // The magic constant 0x0230 will translate "-_" + "+/". mul(iszero(fileSafe), 0x0230) ) ) // Skip the first slot, which stores the length. let ptr := add(add(buffer, 0x20), mload(buffer)) let end := add(data, dataLength) // Run over the input, 3 bytes at a time. // prettier-ignore // solhint-disable-next-line no-empty-blocks for {} 1 {} { data := add(data, 3) // Advance 3 bytes. let input := mload(data) // Write 4 bytes. Optimized for fewer stack operations. mstore8( ptr , mload(and(shr(18, input), 0x3F))) mstore8(add(ptr, 1), mload(and(shr(12, input), 0x3F))) mstore8(add(ptr, 2), mload(and(shr( 6, input), 0x3F))) mstore8(add(ptr, 3), mload(and( input , 0x3F))) ptr := add(ptr, 4) // Advance 4 bytes. // prettier-ignore if iszero(lt(data, end)) { break } } if iszero(noPadding) { // Offset `ptr` and pad with '='. We can simply write over the end. mstore8(sub(ptr, iszero(iszero(r))), 0x3d) // Pad at `ptr - 1` if `r > 0`. mstore8(sub(ptr, shl(1, eq(r, 1))), 0x3d) // Pad at `ptr - 2` if `r == 1`. } mstore(buffer, add(mload(buffer), encodedLength)) mstore(0x40, nextFree) } } /// @notice Returns the capacity of a given buffer. function capacity(bytes memory buffer) internal pure returns (uint256) { uint256 cap; assembly { cap := sub(mload(sub(buffer, 0x20)), 0x40) } return cap; } /// @notice Reverts if the buffer will overflow after appending a given /// number of bytes. function checkOverflow(bytes memory buffer, uint256 addedLength) internal pure { uint256 cap = capacity(buffer); uint256 newLength = buffer.length + addedLength; if (cap < newLength) { revert("DynamicBuffer: Appending out of bounds."); } } }