// 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) calldatacopy(result, calldatasize(), 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 countLeadingZeroBytes(x_) -> _r { _r := shl(7, lt(0xffffffffffffffffffffffffffffffff, x_)) _r := or(_r, shl(6, lt(0xffffffffffffffff, shr(_r, x_)))) _r := or(_r, shl(5, lt(0xffffffff, shr(_r, x_)))) _r := or(_r, shl(4, lt(0xffff, shr(_r, x_)))) _r := xor(31, or(shr(3, _r), lt(0xff, shr(_r, x_)))) } function min(x_, y_) -> _z { _z := xor(x_, mul(xor(x_, y_), lt(y_, x_))) } result := mload(0x40) let end := add(data, mload(data)) let m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f let o := add(result, 0x20) for { let i := data } iszero(eq(i, end)) {} { i := add(i, 1) let c := byte(31, mload(i)) if iszero(c) { for {} 1 {} { let x := mload(add(i, 0x20)) if iszero(x) { let r := min(sub(end, i), 0x20) r := min(sub(0x7f, c), r) i := add(i, r) c := add(c, r) if iszero(gt(r, 0x1f)) { break } continue } let r := countLeadingZeroBytes(x) r := min(sub(end, i), r) i := add(i, r) c := add(c, r) break } mstore(o, shl(240, c)) o := add(o, 2) continue } if eq(c, 0xff) { let r := 0x20 let x := not(mload(add(i, r))) if x { r := countLeadingZeroBytes(x) } r := min(min(sub(end, i), r), 0x1f) i := add(i, r) mstore(o, shl(240, or(r, 0x80))) o := add(o, 2) continue } mstore8(o, c) o := add(o, 1) c := mload(add(i, 0x20)) mstore(o, c) // `.each(b => b == 0x00 || b == 0xff ? 0x80 : 0x00)`. c := not(or(and(or(add(and(c, m), m), c), or(add(and(not(c), m), m), not(c))), m)) let r := shl(7, lt(0x8421084210842108cc6318c6db6d54be, c)) // Save bytecode. r := or(shl(6, lt(0xffffffffffffffff, shr(r, c))), r) // forgefmt: disable-next-item r := add(iszero(c), shr(3, xor(byte(and(0x1f, shr(byte(24, mul(0x02040810204081, shr(r, c))), 0x8421084210842108cc6318c6db6d54be)), 0xc0c8c8d0c8e8d0d8c8e8e0e8d0d8e0f0c8d0e8d0e0e0d8f0d0d0e0d8f8f8f8f8), r))) r := min(sub(end, i), r) o := add(o, r) i := add(i, r) } // 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 s := add(data, 4) let v := mload(s) let end := add(add(0x20, data), mload(data)) let m := 0x7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f let o := add(result, 0x20) mstore(s, not(v)) // Bitwise negate the first 4 bytes. for { let i := add(0x20, data) } 1 {} { let c := mload(i) if iszero(byte(0, c)) { c := add(1, byte(1, c)) if iszero(gt(c, 0x80)) { i := add(i, 2) calldatacopy(o, calldatasize(), c) // Fill with 0x00. o := add(o, c) if iszero(lt(i, end)) { break } continue } i := add(i, 2) mstore(o, not(0)) // Fill with 0xff. o := add(o, sub(c, 0x80)) if iszero(lt(i, end)) { break } continue } mstore(o, c) c := not(or(or(add(and(c, m), m), c), m)) // `.each(b => b == 0x00 ? 0x80 : 0x00)`. let r := shl(7, lt(0x8421084210842108cc6318c6db6d54be, c)) // Save bytecode. r := or(shl(6, lt(0xffffffffffffffff, shr(r, c))), r) // forgefmt: disable-next-item c := add(iszero(c), shr(3, xor(byte(and(0x1f, shr(byte(24, mul(0x02040810204081, shr(r, c))), 0x8421084210842108cc6318c6db6d54be)), 0xc0c8c8d0c8e8d0d8c8e8e0e8d0d8e0f0c8d0e8d0e0e0d8f0d0d0e0d8f8f8f8f8), r))) o := add(o, c) i := add(i, c) if lt(i, end) { continue } if gt(i, end) { o := sub(o, sub(i, end)) } break } 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 { /// @solidity memory-safe-assembly 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)) { calldatacopy(o, calldatasize(), 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()) } } }