// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Library for compressing and decompressing bytes. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibZip.sol) /// @author Calldata compression by clabby (https://github.com/clabby/op-kompressor) /// @author FastLZ by ariya (https://github.com/ariya/FastLZ) /// /// @dev Note: /// The accompanying solady.js library includes implementations of /// FastLZ and calldata operations for convenience. library LibZip { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* FAST LZ OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // LZ77 implementation based on FastLZ. // Equivalent to level 1 compression and decompression at the following commit: // https://github.com/ariya/FastLZ/commit/344eb4025f9ae866ebf7a2ec48850f7113a97a42 // Decompression is backwards compatible. /// @dev Returns the compressed `data`. function flzCompress(bytes memory data) internal pure returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { function ms8(d_, v_) -> _d { mstore8(d_, v_) _d := add(d_, 1) } function u24(p_) -> _u { _u := mload(p_) _u := or(shl(16, byte(2, _u)), or(shl(8, byte(1, _u)), byte(0, _u))) } function cmp(p_, q_, e_) -> _l { for { e_ := sub(e_, q_) } lt(_l, e_) { _l := add(_l, 1) } { e_ := mul(iszero(byte(0, xor(mload(add(p_, _l)), mload(add(q_, _l))))), e_) } } function literals(runs_, src_, dest_) -> _o { for { _o := dest_ } iszero(lt(runs_, 0x20)) { runs_ := sub(runs_, 0x20) } { mstore(ms8(_o, 31), mload(src_)) _o := add(_o, 0x21) src_ := add(src_, 0x20) } if iszero(runs_) { leave } mstore(ms8(_o, sub(runs_, 1)), mload(src_)) _o := add(1, add(_o, runs_)) } function mt(l_, d_, o_) -> _o { for { d_ := sub(d_, 1) } iszero(lt(l_, 263)) { l_ := sub(l_, 262) } { o_ := ms8(ms8(ms8(o_, add(224, shr(8, d_))), 253), and(0xff, d_)) } if iszero(lt(l_, 7)) { _o := ms8(ms8(ms8(o_, add(224, shr(8, d_))), sub(l_, 7)), and(0xff, d_)) leave } _o := ms8(ms8(o_, add(shl(5, l_), shr(8, d_))), and(0xff, d_)) } function setHash(i_, v_) { let p_ := add(mload(0x40), shl(2, i_)) mstore(p_, xor(mload(p_), shl(224, xor(shr(224, mload(p_)), v_)))) } function getHash(i_) -> _h { _h := shr(224, mload(add(mload(0x40), shl(2, i_)))) } function hash(v_) -> _r { _r := and(shr(19, mul(2654435769, v_)), 0x1fff) } function setNextHash(ip_, ipStart_) -> _ip { setHash(hash(u24(ip_)), sub(ip_, ipStart_)) _ip := add(ip_, 1) } result := mload(0x40) codecopy(result, codesize(), 0x8000) // Zeroize the hashmap. let op := add(result, 0x8000) let a := add(data, 0x20) let ipStart := a let ipLimit := sub(add(ipStart, mload(data)), 13) for { let ip := add(2, a) } lt(ip, ipLimit) {} { let r := 0 let d := 0 for {} 1 {} { let s := u24(ip) let h := hash(s) r := add(ipStart, getHash(h)) setHash(h, sub(ip, ipStart)) d := sub(ip, r) if iszero(lt(ip, ipLimit)) { break } ip := add(ip, 1) if iszero(gt(d, 0x1fff)) { if eq(s, u24(r)) { break } } } if iszero(lt(ip, ipLimit)) { break } ip := sub(ip, 1) if gt(ip, a) { op := literals(sub(ip, a), a, op) } let l := cmp(add(r, 3), add(ip, 3), add(ipLimit, 9)) op := mt(l, d, op) ip := setNextHash(setNextHash(add(ip, l), ipStart), ipStart) a := ip } // Copy the result to compact the memory, overwriting the hashmap. let end := sub(literals(sub(add(ipStart, mload(data)), a), a, op), 0x7fe0) let o := add(result, 0x20) mstore(result, sub(end, o)) // Store the length. for {} iszero(gt(o, end)) { o := add(o, 0x20) } { mstore(o, mload(add(o, 0x7fe0))) } mstore(end, 0) // Zeroize the slot after the string. mstore(0x40, add(end, 0x20)) // Allocate the memory. } } /// @dev Returns the decompressed `data`. function flzDecompress(bytes memory data) internal pure returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { result := mload(0x40) let op := add(result, 0x20) let end := add(add(data, 0x20), mload(data)) for { data := add(data, 0x20) } lt(data, end) {} { let w := mload(data) let c := byte(0, w) let t := shr(5, c) if iszero(t) { mstore(op, mload(add(data, 1))) data := add(data, add(2, c)) op := add(op, add(1, c)) continue } for { let g := eq(t, 7) let l := add(2, xor(t, mul(g, xor(t, add(7, byte(1, w)))))) // M let s := add(add(shl(8, and(0x1f, c)), byte(add(1, g), w)), 1) // R let r := sub(op, s) let f := xor(s, mul(gt(s, 0x20), xor(s, 0x20))) let j := 0 } 1 {} { mstore(add(op, j), mload(add(r, j))) j := add(j, f) if lt(j, l) { continue } data := add(data, add(2, g)) op := add(op, l) break } } mstore(result, sub(op, add(result, 0x20))) // Store the length. mstore(op, 0) // Zeroize the slot after the string. mstore(0x40, add(op, 0x20)) // Allocate the memory. } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CALLDATA OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // Calldata compression and decompression using selective run length encoding: // - Sequences of 0x00 (up to 128 consecutive). // - Sequences of 0xff (up to 32 consecutive). // // A run length encoded block consists of two bytes: // (0) 0x00 // (1) A control byte with the following bit layout: // - [7] `0: 0x00, 1: 0xff`. // - [0..6] `runLength - 1`. // // The first 4 bytes are bitwise negated so that the compressed calldata // can be dispatched into the `fallback` and `receive` functions. /// @dev Returns the compressed `data`. function cdCompress(bytes memory data) internal pure returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { function rle(v_, o_, d_) -> _o, _d { mstore(o_, shl(240, or(and(0xff, add(d_, 0xff)), and(0x80, v_)))) _o := add(o_, 2) } result := mload(0x40) let o := add(result, 0x20) let z := 0 // Number of consecutive 0x00. let y := 0 // Number of consecutive 0xff. for { let end := add(data, mload(data)) } iszero(eq(data, end)) {} { data := add(data, 1) let c := byte(31, mload(data)) if iszero(c) { if y { o, y := rle(0xff, o, y) } z := add(z, 1) if eq(z, 0x80) { o, z := rle(0x00, o, 0x80) } continue } if eq(c, 0xff) { if z { o, z := rle(0x00, o, z) } y := add(y, 1) if eq(y, 0x20) { o, y := rle(0xff, o, 0x20) } continue } if y { o, y := rle(0xff, o, y) } if z { o, z := rle(0x00, o, z) } mstore8(o, c) o := add(o, 1) } if y { o, y := rle(0xff, o, y) } if z { o, z := rle(0x00, o, z) } // Bitwise negate the first 4 bytes. mstore(add(result, 4), not(mload(add(result, 4)))) mstore(result, sub(o, add(result, 0x20))) // Store the length. mstore(o, 0) // Zeroize the slot after the string. mstore(0x40, add(o, 0x20)) // Allocate the memory. } } /// @dev Returns the decompressed `data`. function cdDecompress(bytes memory data) internal pure returns (bytes memory result) { /// @solidity memory-safe-assembly assembly { if mload(data) { result := mload(0x40) let o := add(result, 0x20) let s := add(data, 4) let v := mload(s) let end := add(data, mload(data)) mstore(s, not(v)) // Bitwise negate the first 4 bytes. for {} lt(data, end) {} { data := add(data, 1) let c := byte(31, mload(data)) if iszero(c) { data := add(data, 1) let d := byte(31, mload(data)) // Fill with either 0xff or 0x00. mstore(o, not(0)) if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) } o := add(o, add(and(d, 0x7f), 1)) continue } mstore8(o, c) o := add(o, 1) } mstore(s, v) // Restore the first 4 bytes. mstore(result, sub(o, add(result, 0x20))) // Store the length. mstore(o, 0) // Zeroize the slot after the string. mstore(0x40, add(o, 0x20)) // Allocate the memory. } } } /// @dev To be called in the `fallback` function. /// ``` /// fallback() external payable { LibZip.cdFallback(); } /// receive() external payable {} // Silence compiler warning to add a `receive` function. /// ``` /// For efficiency, this function will directly return the results, terminating the context. /// If called internally, it must be called at the end of the function. function cdFallback() internal { assembly { if iszero(calldatasize()) { return(calldatasize(), calldatasize()) } let o := 0 let f := not(3) // For negating the first 4 bytes. for { let i := 0 } lt(i, calldatasize()) {} { let c := byte(0, xor(add(i, f), calldataload(i))) i := add(i, 1) if iszero(c) { let d := byte(0, xor(add(i, f), calldataload(i))) i := add(i, 1) // Fill with either 0xff or 0x00. mstore(o, not(0)) if iszero(gt(d, 0x7f)) { codecopy(o, codesize(), add(d, 1)) } o := add(o, add(and(d, 0x7f), 1)) continue } mstore8(o, c) o := add(o, 1) } let success := delegatecall(gas(), address(), 0x00, o, codesize(), 0x00) returndatacopy(0x00, 0x00, returndatasize()) if iszero(success) { revert(0x00, returndatasize()) } return(0x00, returndatasize()) } } }