Cryo Explorer Ethereum Mainnet

Address Contract Verified

Address 0x2717DA58d9934C2b37f25B786B8C9122F7f39BEf
Balance 0 ETH
Nonce 1095
Code Size 7186 bytes
Indexed Transactions 0
External Etherscan · Sourcify

Contract Bytecode

7186 bytes
0x60806040526004361061018e575f3560e01c80637df325e1116100dc578063b2118a8d11610087578063c44c677411610062578063c44c677414610433578063d9072bb414610452578063dcceaea214610471578063f2fde38b14610490575f5ffd5b8063b2118a8d146103e2578063b4b5b48f146103f5578063c278ede614610414575f5ffd5b80638da5cb5b116100b75780638da5cb5b1461037d5780639311ca89146103b0578063a0808b32146103c3575f5ffd5b80637df325e1146103385780637f1f0c671461034b578063861daab91461035e575f5ffd5b8063372df8fe1161013c5780635e969257116101175780635e969257146102e65780636031680114610305578063715018a614610324575f5ffd5b8063372df8fe14610289578063445db02a146102a8578063510ef1de146102c7575f5ffd5b80630deaaf4c1161016c5780630deaaf4c146102195780631135c5d21461023a5780633571ae8f1461024d575f5ffd5b8063099a04e5146101925780630aebf22c146101a75780630bac2d0a146101ed575b5f5ffd5b6101a56101a0366004611435565b6104af565b005b3480156101b2575f5ffd5b506101d86101c136600461145d565b5f9081526001602081905260409091200154151590565b60405190151581526020015b60405180910390f35b3480156101f8575f5ffd5b5061020c61020736600461145d565b6104d8565b6040516101e49190611474565b348015610224575f5ffd5b5061022d610551565b6040516101e49190611518565b6101a561024836600461152a565b6105dd565b348015610258575f5ffd5b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffb8e2915b546040519081526020016101e4565b348015610294575f5ffd5b5061022d6102a336600461145d565b61064c565b3480156102b3575f5ffd5b5061022d6102c236600461145d565b610685565b3480156102d2575f5ffd5b506101a56102e13660046116ab565b610696565b3480156102f1575f5ffd5b5061022d61030036600461145d565b6106b5565b348015610310575f5ffd5b5061022d61031f36600461145d565b6107c0565b34801561032f575f5ffd5b506101a5610883565b6101a56103463660046116ef565b610896565b6101a561035936600461145d565b6108e2565b348015610369575f5ffd5b5061022d61037836600461145d565b61091c565b348015610388575f5ffd5b505f5460405173ffffffffffffffffffffffffffffffffffffffff90911681526020016101e4565b6101a56103be366004611729565b610927565b3480156103ce575f5ffd5b5061022d6103dd366004611768565b610971565b6101a56103f03660046116ef565b61098c565b348015610400575f5ffd5b5061022d61040f36600461145d565b6109cb565b34801561041f575f5ffd5b506101a561042e3660046117f5565b610a66565b34801561043e575f5ffd5b506101a561044d36600461183d565b610b5d565b34801561045d575f5ffd5b506101a561046c3660046118ad565b610c22565b34801561047c575f5ffd5b5061022d61048b366004611768565b610c36565b34801561049b575f5ffd5b506101a56104aa3660046118df565b610c41565b60086104ba81610cf8565b5f385f3885875af16104d357637ec62e765f526004601cfd5b505050565b5f8181526001602081815260409283902090910180548351818402810184019094528084526060939283018282801561054557602002820191905f5260205f20905b815473ffffffffffffffffffffffffffffffffffffffff16815260019091019060200180831161051a575b50505050509050919050565b6002805461055e906118f8565b80601f016020809104026020016040519081016040528092919081815260200182805461058a906118f8565b80156105d55780601f106105ac576101008083540402835291602001916105d5565b820191905f5260205f20905b8154815290600101906020018083116105b857829003601f168201915b505050505081565b60406105e881610cf8565b604051677ec62e76f242432a81523081602001528660601b60601c816040015285816060015284816080015260a08160a00152828160c00152828460e08301375f388460c401601c8401348c5af161064257600460188201fd5b5050505050505050565b606061065f61065a836106b5565b610d93565b60405160200161066f9190611960565b6040516020818303038152906040529050919050565b60606106908261064c565b92915050565b61069e610da0565b5f8281526001602052604090206104d382826119d5565b5f8181526001602081905260408220015460609103610708576040517f62ca3658000000000000000000000000000000000000000000000000000000008152600481018390526024015b60405180910390fd5b60605f5b5f848152600160208190526040909120015460ff821610156107af578161077a60015f8781526020019081526020015f206001018360ff168154811061075457610754611aec565b5f9182526020909120015473ffffffffffffffffffffffffffffffffffffffff16610e20565b60405160200161078b929190611b19565b604051602081830303815290604052915080806107a790611b2d565b91505061070c565b506107b981610e6b565b9392505050565b5f818152600160205260408120805460609291906107dd906118f8565b90501115610876575f82815260016020526040902080546107fd906118f8565b80601f0160208091040260200160405190810160405280929190818152602001828054610829906118f8565b80156105455780601f1061084b57610100808354040283529160200191610545565b820191905f5260205f20905b8154815290600101906020018083116108575750939695505050505050565b600280546107fd906118f8565b61088b610da0565b6108945f610f45565b565b60206108a181610cf8565b604051826060528360601b60601c60405230602052677ec62e7623b872dd5f525f386064601c34895af16108d55760046018fd5b5f60605260405250505050565b60046108ed81610cf8565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffb8e2915b8054831790555050565b5050565b6060610690826106b5565b608061093281610cf8565b604051846014528360345282605452677ec62e76095bcdb660601b5f525f3860646010348a5af1610963576004600cfd5b5f6060526040525050505050565b606061097c82610fb9565b60405160200161066f9190611b70565b601061099781610cf8565b8260145281603452677ec62e76a9059cbb60601b5f525f386044601034885af16109c1576004600cfd5b5f60345250505050565b60016020525f90815260409020805481906109e5906118f8565b80601f0160208091040260200160405190810160405280929190818152602001828054610a11906118f8565b8015610a5c5780601f10610a3357610100808354040283529160200191610a5c565b820191905f5260205f20905b815481529060010190602001808311610a3f57829003601f168201915b5050505050905081565b610a6e610da0565b5f5b60ff8116821115610b57575f84815260016020819052604090912001610aef848460ff8516818110610aa457610aa4611aec565b9050602002810190610ab69190611b7b565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506112bf92505050565b81546001810183555f928352602090922090910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90921691909117905580610b4f81611b2d565b915050610a70565b50505050565b610b65610da0565b5f848152600160205260409020610b7c84826119d5565b505f5b60ff8116821115610c1b575f85815260016020819052604090912001610bb3848460ff8516818110610aa457610aa4611aec565b81546001810183555f928352602090922090910180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff90921691909117905580610c1381611b2d565b915050610b7f565b5050505050565b610c2a610da0565b600261091882826119d5565b606061097c82610e6b565b610c49610da0565b73ffffffffffffffffffffffffffffffffffffffff8116610cec576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201527f646472657373000000000000000000000000000000000000000000000000000060648201526084016106ff565b610cf581610f45565b50565b5f610d217fffffffffffffffffffffffffffffffffffffffffffffffffffffffffb8e2915b5490565b90507fe8a2161d8f3f215f8491de5dbb7b64d23cbaffd8ded0e6f81a2e29fc09257a098182841615155f03179150336020523060018316528060405f2003156104d357678da5cb5b0a0362e060085260205f60046020305afa335f5114601f3d11166002841610166104d35760046024fd5b6060610690825f5f611300565b5f5473ffffffffffffffffffffffffffffffffffffffff163314610894576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e657260448201526064016106ff565b60405164ffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff833b0116602181015f601f8401853c80825260408201810160405250919050565b60405181516020838101938184019201015b80841015610f0f578351805f1a8060051c80610eaf575060018681015185529581016002019593019092019150610e7d565b600781148360011a600701821881028218600201600185836001011a85601f1660081b0101808803602082186020831102821891505f5b818101518a8201528201838110610ee6579a84016002019a988301985050505050505050610e7d565b507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08282030182525f8152602001604052919050565b5f805473ffffffffffffffffffffffffffffffffffffffff8381167fffffffffffffffffffffffff0000000000000000000000000000000000000000831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b6060611145565b818153600101919050565b5f82840393505b838110156107b9578281015182820151185f1a1590930292600101610fd2565b825b6020821061103e578251611009601f83610fc0565b52602092909201917fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe090910190602101610ff4565b81156107b95782516110536001840383610fc0565b520160010192915050565b5f6001830392505b610107821061109e576110908360ff1661108b60fd61108b8760081c60e00189610fc0565b610fc0565b935061010682039150611066565b600782106110cb576110c48360ff1661108b6007850361108b8760081c60e00189610fc0565b90506107b9565b6110e48360ff1661108b8560081c8560051b0187610fc0565b949350505050565b805161113d9083830390611121905f81901a600182901a60081b1760029190911a60101b17639e3779b90260131c611fff1690565b8060021b6040510182815160e01c1860e01b8151188152505050565b600101919050565b5060405161800036823761800081016020830180600d8551820103826002015b81811015611275575f5f5b505080516040515f82901a600183901a60081b1760029290921a60101b91909117639e3779b9810260111c617ffc16909101805160e081811c878603811890911b909118909152840190818303908484106111cb5750611205565b600184019350611fff82116111ff5782515f81901a600182901a60081b1760029190911a60101b1781036111ff5750611205565b50611170565b838310611213575050611275565b600183039250858311156112315761122e8787888603610ff2565b96505b611245600985016003850160038501610fcb565b915061125287828461105e565b96505061126a84611265868486016110ec565b6110ec565b915050809350611165565b5050617fe061128a8484858951860103610ff2565b03925050506020820180820383525b8181116112b057617fe08101518152602001611299565b505f8152602001604052919050565b5f81518060401b6bfe61000180600a3d393df3000161fffe8211840152600b8101601584015ff09150816112fa5763301164255f526004601cfd5b90915290565b606083518015611405576003600282010460021b60405192507f4142434445464748494a4b4c4d4e4f505152535455565758595a616263646566601f526106708515027f6768696a6b6c6d6e6f707172737475767778797a303132333435363738392d5f18603f526020830181810183886020010180515f82525b60038a0199508951603f8160121c16515f53603f81600c1c1651600153603f8160061c1651600253603f811651600353505f51845260048401935082841061137b5790526020016040527f3d3d00000000000000000000000000000000000000000000000000000000000060038406600204808303919091525f8615159091029182900352900382525b509392505050565b803573ffffffffffffffffffffffffffffffffffffffff81168114611430575f5ffd5b919050565b5f5f60408385031215611446575f5ffd5b61144f8361140d565b946020939093013593505050565b5f6020828403121561146d575f5ffd5b5035919050565b602080825282518282018190525f918401906040840190835b818110156114c157835173ffffffffffffffffffffffffffffffffffffffff1683526020938401939092019160010161148d565b509095945050505050565b5f81518084528060208401602086015e5f6020828601015260207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f83011685010191505092915050565b602081525f6107b960208301846114cc565b5f5f5f5f5f5f60a0878903121561153f575f5ffd5b6115488761140d565b95506115566020880161140d565b94506040870135935060608701359250608087013567ffffffffffffffff81111561157f575f5ffd5b8701601f8101891361158f575f5ffd5b803567ffffffffffffffff8111156115a5575f5ffd5b8960208284010111156115b6575f5ffd5b60208201935080925050509295509295509295565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b5f5f67ffffffffffffffff841115611612576116126115cb565b506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f85018116603f0116810181811067ffffffffffffffff8211171561165f5761165f6115cb565b604052838152905080828401851015611676575f5ffd5b838360208301375f60208583010152509392505050565b5f82601f83011261169c575f5ffd5b6107b9838335602085016115f8565b5f5f604083850312156116bc575f5ffd5b82359150602083013567ffffffffffffffff8111156116d9575f5ffd5b6116e58582860161168d565b9150509250929050565b5f5f5f60608486031215611701575f5ffd5b61170a8461140d565b92506117186020850161140d565b929592945050506040919091013590565b5f5f5f5f6080858703121561173c575f5ffd5b6117458561140d565b93506117536020860161140d565b93969395505050506040820135916060013590565b5f60208284031215611778575f5ffd5b813567ffffffffffffffff81111561178e575f5ffd5b8201601f8101841361179e575f5ffd5b6110e4848235602084016115f8565b5f5f83601f8401126117bd575f5ffd5b50813567ffffffffffffffff8111156117d4575f5ffd5b6020830191508360208260051b85010111156117ee575f5ffd5b9250929050565b5f5f5f60408486031215611807575f5ffd5b83359250602084013567ffffffffffffffff811115611824575f5ffd5b611830868287016117ad565b9497909650939450505050565b5f5f5f5f60608587031215611850575f5ffd5b84359350602085013567ffffffffffffffff81111561186d575f5ffd5b6118798782880161168d565b935050604085013567ffffffffffffffff811115611895575f5ffd5b6118a1878288016117ad565b95989497509550505050565b5f602082840312156118bd575f5ffd5b813567ffffffffffffffff8111156118d3575f5ffd5b6110e48482850161168d565b5f602082840312156118ef575f5ffd5b6107b98261140d565b600181811c9082168061190c57607f821691505b602082108103611943577f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b50919050565b5f81518060208401855e5f93019283525090919050565b7f646174613a696d6167652f7376672b786d6c3b6261736536342c00000000000081525f6107b9601a830184611949565b601f8211156104d357805f5260205f20601f840160051c810160208510156119b65750805b601f840160051c820191505b81811015610c1b575f81556001016119c2565b815167ffffffffffffffff8111156119ef576119ef6115cb565b611a03816119fd84546118f8565b84611991565b6020601f821160018114611a54575f8315611a1e5750848201515b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600385901b1c1916600184901b178455610c1b565b5f848152602081207fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08516915b82811015611aa15787850151825560209485019460019092019101611a81565b5084821015611add57868401517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff600387901b60f8161c191681555b50505050600190811b01905550565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52603260045260245ffd5b5f6110e4611b278386611949565b84611949565b5f60ff821660ff8103611b67577f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b60010192915050565b5f6107b98284611949565b5f5f83357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112611bae575f5ffd5b83018035915067ffffffffffffffff821115611bc8575f5ffd5b6020019150368190038213156117ee575f5ffdfea26469706673582212207c39b07dee114b9cd153f97a84f84c86642187e522aaa8a0cfc62b5162609bf764736f6c634300081e0033

Verified Source Code Full Match

Compiler: v0.8.30+commit.73712a01 EVM: cancun Optimization: Yes (10000 runs)
Ownable.sol 83 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
Context.sol 24 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
Base64.sol 175 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Library to encode strings in Base64.
/// @author 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 - <[email protected]>.
library Base64 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    ENCODING / DECODING                     */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// See: https://datatracker.ietf.org/doc/html/rfc4648
    /// @param fileSafe  Whether to replace '+' with '-' and '/' with '_'.
    /// @param noPadding Whether to strip away the padding.
    function encode(bytes memory data, bool fileSafe, bool noPadding)
        internal
        pure
        returns (string memory result)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let dataLength := mload(data)

            if dataLength {
                // Multiply by 4/3 rounded up.
                // The `shl(2, ...)` is equivalent to multiplying by 4.
                let encodedLength := shl(2, div(add(dataLength, 2), 3))

                // Set `result` to point to the start of the free memory.
                result := 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.
                // The magic constant 0x0670 will turn "-_" into "+/".
                mstore(0x1f, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef")
                mstore(0x3f, xor("ghijklmnopqrstuvwxyz0123456789-_", mul(iszero(fileSafe), 0x0670)))

                // Skip the first slot, which stores the length.
                let ptr := add(result, 0x20)
                let end := add(ptr, encodedLength)

                let dataEnd := add(add(0x20, data), dataLength)
                let dataEndValue := mload(dataEnd) // Cache the value at the `dataEnd` slot.
                mstore(dataEnd, 0x00) // Zeroize the `dataEnd` slot to clear dirty bits.

                // Run over the input, 3 bytes at a time.
                for {} 1 {} {
                    data := add(data, 3) // Advance 3 bytes.
                    let input := mload(data)

                    // Write 4 bytes. Optimized for fewer stack operations.
                    mstore8(0, mload(and(shr(18, input), 0x3F)))
                    mstore8(1, mload(and(shr(12, input), 0x3F)))
                    mstore8(2, mload(and(shr(6, input), 0x3F)))
                    mstore8(3, mload(and(input, 0x3F)))
                    mstore(ptr, mload(0x00))

                    ptr := add(ptr, 4) // Advance 4 bytes.
                    if iszero(lt(ptr, end)) { break }
                }
                mstore(dataEnd, dataEndValue) // Restore the cached value at `dataEnd`.
                mstore(0x40, add(end, 0x20)) // Allocate the memory.
                // Equivalent to `o = [0, 2, 1][dataLength % 3]`.
                let o := div(2, mod(dataLength, 3))
                // Offset `ptr` and pad with '='. We can simply write over the end.
                mstore(sub(ptr, o), shl(240, 0x3d3d))
                // Set `o` to zero if there is padding.
                o := mul(iszero(iszero(noPadding)), o)
                mstore(sub(ptr, o), 0) // Zeroize the slot after the string.
                mstore(result, sub(encodedLength, o)) // Store the length.
            }
        }
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, false, false)`.
    function encode(bytes memory data) internal pure returns (string memory result) {
        result = encode(data, false, false);
    }

    /// @dev Encodes `data` using the base64 encoding described in RFC 4648.
    /// Equivalent to `encode(data, fileSafe, false)`.
    function encode(bytes memory data, bool fileSafe)
        internal
        pure
        returns (string memory result)
    {
        result = encode(data, fileSafe, false);
    }

    /// @dev Decodes base64 encoded `data`.
    ///
    /// Supports:
    /// - RFC 4648 (both standard and file-safe mode).
    /// - RFC 3501 (63: ',').
    ///
    /// Does not support:
    /// - Line breaks.
    ///
    /// Note: For performance reasons,
    /// this function will NOT revert on invalid `data` inputs.
    /// Outputs for invalid inputs will simply be undefined behaviour.
    /// It is the user's responsibility to ensure that the `data`
    /// is a valid base64 encoded string.
    function decode(string memory data) internal pure returns (bytes memory result) {
        /// @solidity memory-safe-assembly
        assembly {
            let dataLength := mload(data)

            if dataLength {
                let decodedLength := mul(shr(2, dataLength), 3)

                for {} 1 {} {
                    // If padded.
                    if iszero(and(dataLength, 3)) {
                        let t := xor(mload(add(data, dataLength)), 0x3d3d)
                        // forgefmt: disable-next-item
                        decodedLength := sub(
                            decodedLength,
                            add(iszero(byte(30, t)), iszero(byte(31, t)))
                        )
                        break
                    }
                    // If non-padded.
                    decodedLength := add(decodedLength, sub(and(dataLength, 3), 1))
                    break
                }
                result := mload(0x40)

                // Write the length of the bytes.
                mstore(result, decodedLength)

                // Skip the first slot, which stores the length.
                let ptr := add(result, 0x20)
                let end := add(ptr, decodedLength)

                // Load the table into the scratch space.
                // Constants are optimized for smaller bytecode with zero gas overhead.
                // `m` also doubles as the mask of the upper 6 bits.
                let m := 0xfc000000fc00686c7074787c8084888c9094989ca0a4a8acb0b4b8bcc0c4c8cc
                mstore(0x5b, m)
                mstore(0x3b, 0x04080c1014181c2024282c3034383c4044484c5054585c6064)
                mstore(0x1a, 0xf8fcf800fcd0d4d8dce0e4e8ecf0f4)

                for {} 1 {} {
                    // Read 4 bytes.
                    data := add(data, 4)
                    let input := mload(data)

                    // Write 3 bytes.
                    // forgefmt: disable-next-item
                    mstore(ptr, or(
                        and(m, mload(byte(28, input))),
                        shr(6, or(
                            and(m, mload(byte(29, input))),
                            shr(6, or(
                                and(m, mload(byte(30, input))),
                                shr(6, mload(byte(31, input)))
                            ))
                        ))
                    ))
                    ptr := add(ptr, 3)
                    if iszero(lt(ptr, end)) { break }
                }
                mstore(0x40, add(end, 0x20)) // Allocate the memory.
                mstore(end, 0) // Zeroize the slot after the bytes.
                mstore(0x60, 0) // Restore the zero slot.
            }
        }
    }
}
LibZip.sol 327 lines
// 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())
        }
    }
}
Lifebuoy.sol 316 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Class that allows for rescue of ETH, ERC20, ERC721 tokens.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/Lifebuoy.sol)
///
/// @dev This contract is created to mitigate the following disasters:
/// - Careless user sends tokens to the wrong chain or wrong contract.
/// - Careless dev deploys a contract without a withdraw function in attempt to rescue
///   careless user's tokens, due to deployment nonce mismatch caused by
///   script misfire / misconfiguration.
/// - Careless dev forgets to add a withdraw function to a NFT sale contract.
///
/// Note: if you are deploying via a untrusted `tx.origin`,
/// you MUST override `_lifebuoyDefaultDeployer` to return a trusted address.
///
/// For best safety:
/// - For non-escrow contracts, inherit Lifebuoy as much as possible,
///   and leave it unlocked.
/// - For escrow contracts, lock access as tight as possible,
///   as soon as possible. Or simply don't inherit Lifebuoy.
/// Escrow: Your contract is designed to hold ETH, ERC20s, ERC721s
/// (e.g. liquidity pools).
///
/// All rescue and rescue authorization functions require either:
/// - Caller is the deployer
///   AND the contract is not a proxy
///   AND `rescueLocked() & _LIFEBUOY_DEPLOYER_ACCESS_LOCK == 0`.
/// - Caller is `owner()`
///   AND `rescueLocked() & _LIFEBUOY_OWNER_ACCESS_LOCK == 0`.
///
/// The choice of using bit flags to represent locked statuses is for
/// efficiency, flexibility, convenience.
///
/// This contract is optimized with a priority on minimal bytecode size,
/// as the methods are not intended to be called often.
contract Lifebuoy {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The caller is not authorized to rescue or lock the rescue function.
    error RescueUnauthorizedOrLocked();

    /// @dev The rescue operation has failed due to a failed transfer.
    error RescueTransferFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    LOCK FLAGS CONSTANTS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    // These flags are kept internal to avoid bloating up the function dispatch.
    // You can just copy paste this into your own code.

    /// @dev Flag to denote that the deployer's access is locked. (1)
    uint256 internal constant _LIFEBUOY_DEPLOYER_ACCESS_LOCK = 1 << 0;

    /// @dev Flag to denote that the `owner()`'s access is locked. (2)
    uint256 internal constant _LIFEBUOY_OWNER_ACCESS_LOCK = 1 << 1;

    /// @dev Flag to denote that the `lockRescue` function is locked. (4)
    uint256 internal constant _LIFEBUOY_LOCK_RESCUE_LOCK = 1 << 2;

    /// @dev Flag to denote that the `rescueETH` function is locked. (8)
    uint256 internal constant _LIFEBUOY_RESCUE_ETH_LOCK = 1 << 3;

    /// @dev Flag to denote that the `rescueERC20` function is locked. (16)
    uint256 internal constant _LIFEBUOY_RESCUE_ERC20_LOCK = 1 << 4;

    /// @dev Flag to denote that the `rescueERC721` function is locked. (32)
    uint256 internal constant _LIFEBUOY_RESCUE_ERC721_LOCK = 1 << 5;

    /// @dev Flag to denote that the `rescueERC1155` function is locked. (64)
    uint256 internal constant _LIFEBUOY_RESCUE_ERC1155_LOCK = 1 << 6;

    /// @dev Flag to denote that the `rescueERC6909` function is locked. (128)
    uint256 internal constant _LIFEBUOY_RESCUE_ERC6909_LOCK = 1 << 7;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         IMMUTABLES                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev For checking that the caller is the deployer and
    /// that the context is not a delegatecall
    /// (so that the implementation deployer cannot drain proxies).
    bytes32 internal immutable _lifebuoyDeployerHash;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                          STORAGE                           */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The rescue locked flags slot is given by:
    /// `bytes32(~uint256(uint32(bytes4(keccak256("_RESCUE_LOCKED_FLAGS_SLOT_NOT")))))`.
    /// It is intentionally chosen to be a high value
    /// to avoid collision with lower slots.
    /// The choice of manual storage layout is to enable compatibility
    /// with both regular and upgradeable contracts.
    bytes32 internal constant _RESCUE_LOCKED_FLAGS_SLOT =
        0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffb8e2915b;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CONSTRUCTOR                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    constructor() payable {
        bytes32 hash;
        uint256 deployer = uint160(_lifebuoyDefaultDeployer());
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, address())
            mstore(0x20, deployer)
            hash := keccak256(0x00, 0x40)
        }
        _lifebuoyDeployerHash = hash;
    }

    /// @dev Returns `tx.origin` by default. Override to return another address if needed.
    ///
    /// Note: If you are deploying via a untrusted `tx.origin` (e.g. ERC4337 bundler)
    /// you MUST override this function to return a trusted address.
    function _lifebuoyDefaultDeployer() internal view virtual returns (address) {
        // I know about EIP7645, and I will stop it if it gets traction.
        // Worse case, I will add an `ecrecover` method. But not today.
        return tx.origin;
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                     RESCUE OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` (in wei) ETH from the current contract to `to`.
    /// Reverts upon failure.
    function rescueETH(address to, uint256 amount)
        public
        payable
        virtual
        onlyRescuer(_LIFEBUOY_RESCUE_ETH_LOCK)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
                mstore(0x00, 0x7ec62e76) // `RescueTransferFailed()`.
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Does not check for existence of token or return data. Reverts upon failure.
    function rescueERC20(address token, address to, uint256 amount)
        public
        payable
        virtual
        onlyRescuer(_LIFEBUOY_RESCUE_ERC20_LOCK)
    {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, amount) // Store the `amount` argument.
            // `RescueTransferFailed()` and `transfer(address,uint256)`.
            mstore(0x00, shl(96, 0x7ec62e76a9059cbb))
            if iszero(call(gas(), token, callvalue(), 0x10, 0x44, codesize(), 0x00)) {
                revert(0x0c, 0x04)
            }
            mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
        }
    }

    /// @dev Sends `id` of ERC721 `token` from the current contract to `to`.
    /// Does not check for existence of token or return data. Reverts upon failure.
    function rescueERC721(address token, address to, uint256 id)
        public
        payable
        virtual
        onlyRescuer(_LIFEBUOY_RESCUE_ERC721_LOCK)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x60, id) // Store the `id` argument.
            mstore(0x40, shr(96, shl(96, to))) // Store the `to` argument.
            mstore(0x20, address()) // Store the `from` argument.
            // `RescueTransferFailed()` and `transferFrom(address,address,uint256)`.
            mstore(0x00, 0x7ec62e7623b872dd)
            if iszero(call(gas(), token, callvalue(), 0x1c, 0x64, codesize(), 0x00)) {
                revert(0x18, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of `id` of ERC1155 `token` from the current contract to `to`.
    /// Does not check for existence of token or return data. Reverts upon failure.
    function rescueERC1155(
        address token,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) public payable virtual onlyRescuer(_LIFEBUOY_RESCUE_ERC1155_LOCK) {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            // `RescueTransferFailed()` and `safeTransferFrom(address,address,uint256,uint256,bytes)`.
            mstore(m, 0x7ec62e76f242432a)
            mstore(add(0x20, m), address()) // Store the `from` argument.
            mstore(add(0x40, m), shr(96, shl(96, to))) // Store the `to` argument.
            mstore(add(0x60, m), id) // Store the `id` argument.
            mstore(add(0x80, m), amount) // Store the `amount` argument.
            mstore(add(0xa0, m), 0xa0) // Store the offset to `data`.
            mstore(add(0xc0, m), data.length)
            calldatacopy(add(m, 0xe0), data.offset, data.length)
            // forgefmt: disable-next-item
            if iszero(
                call(gas(), token, callvalue(), add(m, 0x1c), add(0xc4, data.length), codesize(), 0x00)
            ) { revert(add(m, 0x18), 0x04) }
        }
    }

    /// @dev Sends `amount` of `id` of ERC6909 `token` from the current contract to `to`.
    /// Does not check for existence of token or return data. Reverts upon failure.
    function rescueERC6909(address token, address to, uint256 id, uint256 amount)
        public
        payable
        virtual
        onlyRescuer(_LIFEBUOY_RESCUE_ERC6909_LOCK)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x14, to) // Store the `to` argument.
            mstore(0x34, id) // Store the `id` argument.
            mstore(0x54, amount) // Store the `amount` argument.
            // `RescueTransferFailed()` and `transfer(address,uint256,uint256)`.
            mstore(0x00, shl(96, 0x7ec62e76095bcdb6))
            if iszero(call(gas(), token, callvalue(), 0x10, 0x64, codesize(), 0x00)) {
                revert(0x0c, 0x04)
            }
            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*              RESCUE AUTHORIZATION OPERATIONS               */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the flags denoting whether access to rescue functions
    /// (including `lockRescue`) is locked.
    function rescueLocked() public view virtual returns (uint256 locks) {
        /// @solidity memory-safe-assembly
        assembly {
            locks := sload(_RESCUE_LOCKED_FLAGS_SLOT)
        }
    }

    /// @dev Locks (i.e. permanently removes) access to rescue functions (including `lockRescue`).
    function lockRescue(uint256 locksToSet)
        public
        payable
        virtual
        onlyRescuer(_LIFEBUOY_LOCK_RESCUE_LOCK)
    {
        _lockRescue(locksToSet);
    }

    /// @dev Internal function to set the lock flags without going through access control.
    function _lockRescue(uint256 locksToSet) internal virtual {
        /// @solidity memory-safe-assembly
        assembly {
            let s := _RESCUE_LOCKED_FLAGS_SLOT
            sstore(s, or(sload(s), locksToSet))
        }
    }

    /// @dev Requires that the rescue function being guarded is:
    /// 1. Not locked, AND
    /// 2. Called by either:
    ///   (a) The `owner()`, OR
    ///   (b) The deployer (if not via a delegate call and deployer is an EOA).
    function _checkRescuer(uint256 modeLock) internal view virtual {
        uint256 locks = rescueLocked();
        bytes32 h = _lifebuoyDeployerHash;
        /// @solidity memory-safe-assembly
        assembly {
            for {} 1 {} {
                // If the `modeLock` flag is true, set all bits in `locks` to true.
                locks := or(sub(0, iszero(iszero(and(modeLock, locks)))), locks)
                // Caller is the deployer
                // AND the contract is not a proxy
                // AND `locks & _LIFEBUOY_DEPLOYER_ACCESS_LOCK` is false.
                mstore(0x20, caller())
                mstore(and(locks, _LIFEBUOY_DEPLOYER_ACCESS_LOCK), address())
                if eq(keccak256(0x00, 0x40), h) { break }
                // If the caller is `owner()`
                // AND `locks & _LIFEBUOY_OWNER_ACCESS_LOCK` is false.
                mstore(0x08, 0x8da5cb5b0a0362e0) // `owner()` and `RescueUnauthorizedOrLocked()`.
                if and( // The arguments of `and` are evaluated from right to left.
                    lt(
                        and(locks, _LIFEBUOY_OWNER_ACCESS_LOCK),
                        and(gt(returndatasize(), 0x1f), eq(mload(0x00), caller()))
                    ),
                    staticcall(gas(), address(), 0x20, 0x04, 0x00, 0x20)
                ) { break }
                revert(0x24, 0x04)
            }
        }
    }

    /// @dev Modifier that calls `_checkRescuer()` at the start of the function.
    modifier onlyRescuer(uint256 modeLock) virtual {
        _checkRescuer(modeLock);
        _;
    }
}
SSTORE2.sol 259 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SSTORE2.sol)
/// @author Saw-mon-and-Natalie (https://github.com/Saw-mon-and-Natalie)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
/// @author Modified from SSTORE3 (https://github.com/Philogy/sstore3)
library SSTORE2 {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The proxy initialization code.
    uint256 private constant _CREATE3_PROXY_INITCODE = 0x67363d3d37363d34f03d5260086018f3;

    /// @dev Hash of the `_CREATE3_PROXY_INITCODE`.
    /// Equivalent to `keccak256(abi.encodePacked(hex"67363d3d37363d34f03d5260086018f3"))`.
    bytes32 internal constant CREATE3_PROXY_INITCODE_HASH =
        0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                        CUSTOM ERRORS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Unable to deploy the storage contract.
    error DeploymentFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         WRITE LOGIC                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Writes `data` into the bytecode of a storage contract and returns its address.
    function write(bytes memory data) internal returns (address pointer) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data) // Let `l` be `n + 1`. +1 as we prefix a STOP opcode.
            /**
             * ---------------------------------------------------+
             * Opcode | Mnemonic       | Stack     | Memory       |
             * ---------------------------------------------------|
             * 61 l   | PUSH2 l        | l         |              |
             * 80     | DUP1           | l l       |              |
             * 60 0xa | PUSH1 0xa      | 0xa l l   |              |
             * 3D     | RETURNDATASIZE | 0 0xa l l |              |
             * 39     | CODECOPY       | l         | [0..l): code |
             * 3D     | RETURNDATASIZE | 0 l       | [0..l): code |
             * F3     | RETURN         |           | [0..l): code |
             * 00     | STOP           |           |              |
             * ---------------------------------------------------+
             * @dev Prefix the bytecode with a STOP opcode to ensure it cannot be called.
             * Also PUSH2 is used since max contract size cap is 24,576 bytes which is less than 2 ** 16.
             */
            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
            // Deploy a new contract with the generated creation code.
            pointer := create(0, add(data, 0x15), add(n, 0xb))
            if iszero(pointer) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /// @dev Writes `data` into the bytecode of a storage contract with `salt`
    /// and returns its normal CREATE2 deterministic address.
    function writeCounterfactual(bytes memory data, bytes32 salt)
        internal
        returns (address pointer)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data)
            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
            // Deploy a new contract with the generated creation code.
            pointer := create2(0, add(data, 0x15), add(n, 0xb), salt)
            if iszero(pointer) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /// @dev Writes `data` into the bytecode of a storage contract and returns its address.
    /// This uses the so-called "CREATE3" workflow,
    /// which means that `pointer` is agnostic to `data, and only depends on `salt`.
    function writeDeterministic(bytes memory data, bytes32 salt)
        internal
        returns (address pointer)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data)
            mstore(0x00, _CREATE3_PROXY_INITCODE) // Store the `_PROXY_INITCODE`.
            let proxy := create2(0, 0x10, 0x10, salt)
            if iszero(proxy) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(0x14, proxy) // Store the proxy's address.
            // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
            // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
            mstore(0x00, 0xd694)
            mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
            pointer := keccak256(0x1e, 0x17)

            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            mstore(add(data, gt(n, 0xfffe)), add(0xfe61000180600a3d393df300, shl(0x40, n)))
            if iszero(
                mul( // The arguments of `mul` are evaluated last to first.
                    extcodesize(pointer),
                    call(gas(), proxy, 0, add(data, 0x15), add(n, 0xb), codesize(), 0x00)
                )
            ) {
                mstore(0x00, 0x30116425) // `DeploymentFailed()`.
                revert(0x1c, 0x04)
            }
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                    ADDRESS CALCULATIONS                    */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns the initialization code hash of the storage contract for `data`.
    /// Used for mining vanity addresses with create2crunch.
    function initCodeHash(bytes memory data) internal pure returns (bytes32 hash) {
        /// @solidity memory-safe-assembly
        assembly {
            let n := mload(data)
            // Do a out-of-gas revert if `n + 1` is more than 2 bytes.
            returndatacopy(returndatasize(), returndatasize(), gt(n, 0xfffe))
            mstore(data, add(0x61000180600a3d393df300, shl(0x40, n)))
            hash := keccak256(add(data, 0x15), add(n, 0xb))
            mstore(data, n) // Restore the length of `data`.
        }
    }

    /// @dev Equivalent to `predictCounterfactualAddress(data, salt, address(this))`
    function predictCounterfactualAddress(bytes memory data, bytes32 salt)
        internal
        view
        returns (address pointer)
    {
        pointer = predictCounterfactualAddress(data, salt, address(this));
    }

    /// @dev Returns the CREATE2 address of the storage contract for `data`
    /// deployed with `salt` by `deployer`.
    /// Note: The returned result has dirty upper 96 bits. Please clean if used in assembly.
    function predictCounterfactualAddress(bytes memory data, bytes32 salt, address deployer)
        internal
        pure
        returns (address predicted)
    {
        bytes32 hash = initCodeHash(data);
        /// @solidity memory-safe-assembly
        assembly {
            // Compute and store the bytecode hash.
            mstore8(0x00, 0xff) // Write the prefix.
            mstore(0x35, hash)
            mstore(0x01, shl(96, deployer))
            mstore(0x15, salt)
            predicted := keccak256(0x00, 0x55)
            // Restore the part of the free memory pointer that has been overwritten.
            mstore(0x35, 0)
        }
    }

    /// @dev Equivalent to `predictDeterministicAddress(salt, address(this))`.
    function predictDeterministicAddress(bytes32 salt) internal view returns (address pointer) {
        pointer = predictDeterministicAddress(salt, address(this));
    }

    /// @dev Returns the "CREATE3" deterministic address for `salt` with `deployer`.
    function predictDeterministicAddress(bytes32 salt, address deployer)
        internal
        pure
        returns (address pointer)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.
            mstore(0x00, deployer) // Store `deployer`.
            mstore8(0x0b, 0xff) // Store the prefix.
            mstore(0x20, salt) // Store the salt.
            mstore(0x40, CREATE3_PROXY_INITCODE_HASH) // Store the bytecode hash.

            mstore(0x14, keccak256(0x0b, 0x55)) // Store the proxy's address.
            mstore(0x40, m) // Restore the free memory pointer.
            // 0xd6 = 0xc0 (short RLP prefix) + 0x16 (length of: 0x94 ++ proxy ++ 0x01).
            // 0x94 = 0x80 + 0x14 (0x14 = the length of an address, 20 bytes, in hex).
            mstore(0x00, 0xd694)
            mstore8(0x34, 0x01) // Nonce of the proxy contract (1).
            pointer := keccak256(0x1e, 0x17)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         READ LOGIC                         */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Equivalent to `read(pointer, 0, 2 ** 256 - 1)`.
    function read(address pointer) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            data := mload(0x40)
            let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
            extcodecopy(pointer, add(data, 0x1f), 0x00, add(n, 0x21))
            mstore(data, n) // Store the length.
            mstore(0x40, add(n, add(data, 0x40))) // Allocate memory.
        }
    }

    /// @dev Equivalent to `read(pointer, start, 2 ** 256 - 1)`.
    function read(address pointer, uint256 start) internal view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            data := mload(0x40)
            let n := and(0xffffffffff, sub(extcodesize(pointer), 0x01))
            let l := sub(n, and(0xffffff, mul(lt(start, n), start)))
            extcodecopy(pointer, add(data, 0x1f), start, add(l, 0x21))
            mstore(data, mul(sub(n, start), lt(start, n))) // Store the length.
            mstore(0x40, add(data, add(0x40, mload(data)))) // Allocate memory.
        }
    }

    /// @dev Returns a slice of the data on `pointer` from `start` to `end`.
    /// `start` and `end` will be clamped to the range `[0, args.length]`.
    /// The `pointer` MUST be deployed via the SSTORE2 write functions.
    /// Otherwise, the behavior is undefined.
    /// Out-of-gas reverts if `pointer` does not have any code.
    function read(address pointer, uint256 start, uint256 end)
        internal
        view
        returns (bytes memory data)
    {
        /// @solidity memory-safe-assembly
        assembly {
            data := mload(0x40)
            if iszero(lt(end, 0xffff)) { end := 0xffff }
            let d := mul(sub(end, start), lt(start, end))
            extcodecopy(pointer, add(data, 0x1f), start, add(d, 0x01))
            if iszero(and(0xff, mload(add(data, d)))) {
                let n := sub(extcodesize(pointer), 0x01)
                returndatacopy(returndatasize(), returndatasize(), shr(40, n))
                d := mul(gt(n, start), sub(d, mul(gt(end, n), sub(end, n))))
            }
            mstore(data, d) // Store the length.
            mstore(add(add(data, 0x20), d), 0) // Zeroize the slot after the bytes.
            mstore(0x40, add(add(data, 0x40), d)) // Allocate memory.
        }
    }
}
SilhouettesStorage.sol 223 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

import { ISilhouettesStorage } from "./interfaces/ISilhouettesStorage.sol";
import { SSTORE2 } from "solady/src/utils/SSTORE2.sol";
import { LibZip } from "solady/src/utils/LibZip.sol";
import { Base64 } from "solady/src/utils/Base64.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Lifebuoy } from "solady/src/utils/Lifebuoy.sol";

/**
 * @title SilhouettesStorage
 * @author Silhouettes by Serc
 * @author Smart Contract by Yigit Duman
 * @author Efficax by diid which inspired this contract vastly
 */
contract SilhouettesStorage is ISilhouettesStorage, Ownable, Lifebuoy {
    /*//////////////////////////////////////////////////////////////
                            ERRORS
    //////////////////////////////////////////////////////////////*/

    error TokenDataNotSet(uint256 tokenId);

    /*//////////////////////////////////////////////////////////////
                                STRUCTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Stores metadata and image data for each token
     * @param metadata JSON metadata string for the token
     * @param chunks Array of SSTORE2 addresses containing compressed image chunks
     */
    struct Token {
        string metadata;
        address[] chunks;
    }

    /*//////////////////////////////////////////////////////////////
                            STATE VARIABLES
    //////////////////////////////////////////////////////////////*/

    /// @notice Mapping from token ID to token data
    mapping(uint256 => Token) public tokenData;

    /// @notice Default token metadata
    string public defaultTokenMetadata;

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor() Ownable() Lifebuoy() { }

    /*//////////////////////////////////////////////////////////////
                      METADATA & IMAGE STORAGE
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Sets the metadata and compressed image data for a token
     * @param tokenId The ID of the token to set data for
     * @param metadata JSON metadata string for the token
     * @param image Array of compressed image chunks to store
     * @dev Only callable by contract owner
     *      WARNING: Uses uint8 for loop counter - limited to 255 chunks max
     *      Each chunk is stored using SSTORE2 for gas efficiency
     */
    function setTokenData(uint256 tokenId, string memory metadata, bytes[] calldata image) external onlyOwner {
        // Set token metadata
        tokenData[tokenId].metadata = metadata;

        // Store each image chunk using SSTORE2 for efficient on-chain storage
        for (uint8 i = 0; i < image.length; i++) {
            tokenData[tokenId].chunks.push(SSTORE2.write(image[i]));
        }
    }

    /**
     * @notice Appends additional compressed image chunks to an existing token
     * @param tokenId The ID of the token to append chunks to
     * @param chunks Array of compressed image chunks to append
     */
    function appendChunks(uint256 tokenId, bytes[] calldata chunks) external onlyOwner {
        for (uint8 i = 0; i < chunks.length; i++) {
            tokenData[tokenId].chunks.push(SSTORE2.write(chunks[i]));
        }
    }

    /**
     * @notice Sets the default token metadata, this is used if the token metadata is not set for a token
     * @param metadata JSON metadata string for the token
     * @dev Only callable by contract owner
     */
    function setDefaultTokenMetadata(string memory metadata) external onlyOwner {
        defaultTokenMetadata = metadata;
    }

    /**
     * @notice Sets the token metadata for a token
     * @param tokenId The ID of the token to set metadata for
     * @param metadata JSON metadata string for the token
     * @dev Only callable by contract owner
     */
    function setTokenMetadata(uint256 tokenId, string memory metadata) external onlyOwner {
        tokenData[tokenId].metadata = metadata;
    }

    /*//////////////////////////////////////////////////////////////
                    IMAGE LOADING & PROCESSING
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Loads and decompresses the raw image data for a token
     * @param tokenId The ID of the token to load image data for
     * @return Decompressed raw image bytes (typically SVG)
     * @dev Reconstructs image by:
     *      1. Reading all SSTORE2 chunks and concatenating them
     *      2. Decompressing using FastLZ (flzDecompress)
     */
    function loadRawImage(uint256 tokenId) public view returns (bytes memory) {
        if (tokenData[tokenId].chunks.length == 0) {
            revert TokenDataNotSet(tokenId);
        }

        bytes memory data;

        // Concatenate all stored chunks from SSTORE2 contracts
        for (uint8 i = 0; i < tokenData[tokenId].chunks.length; i++) {
            data = abi.encodePacked(data, SSTORE2.read(tokenData[tokenId].chunks[i]));
        }

        // FastLZ decompression
        data = LibZip.flzDecompress(data);

        return data;
    }

    /**
     * @notice Loads the image as a base64-encoded data URI
     * @param tokenId The ID of the token to load image for
     * @return Base64-encoded SVG data URI ready for use in metadata
     * @dev Calls loadRawImage() and encodes result as data:image/svg+xml;base64,[data]
     */
    function loadImage(uint256 tokenId) public view returns (string memory) {
        return string(abi.encodePacked("data:image/svg+xml;base64,", Base64.encode(loadRawImage(tokenId))));
    }

    /*//////////////////////////////////////////////////////////////
                      INTERFACE IMPLEMENTATIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Returns the image URI for a token (base64-encoded data URI)
     * @param tokenId The ID of the token
     * @return Base64-encoded SVG data URI
     */
    function getTokenImageURI(uint256 tokenId) external view override returns (string memory) {
        return loadImage(tokenId);
    }

    /**
     * @notice Returns the raw decompressed image data for a token
     * @param tokenId The ID of the token
     * @return Raw image bytes (typically SVG)
     */
    function getTokenRawImage(uint256 tokenId) external view override returns (bytes memory) {
        return loadRawImage(tokenId);
    }

    /**
     * @notice Returns the metadata for a token, falls back to default if not set
     * @param tokenId The ID of the token
     * @return JSON metadata string
     */
    function getTokenMetadata(uint256 tokenId) external view override returns (string memory) {
        if (bytes(tokenData[tokenId].metadata).length > 0) {
            return tokenData[tokenId].metadata;
        }
        return defaultTokenMetadata;
    }

    /**
     * @notice Returns the array of SSTORE2 chunk addresses for a token
     * @param tokenId The ID of the token
     * @return Array of chunk addresses
     */
    function getTokenChunks(uint256 tokenId) external view override returns (address[] memory) {
        return tokenData[tokenId].chunks;
    }

    /**
     * @notice Checks if the token data is set for a token
     * @param tokenId The ID of the token to check
     * @return True if the token data is set, false otherwise
     */
    function isTokenDataSet(uint256 tokenId) external view override returns (bool) {
        return tokenData[tokenId].chunks.length > 0;
    }

    /*//////////////////////////////////////////////////////////////
                           UTILITY FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Utility function to decompress FastLZ compressed data
     * @param data FastLZ compressed bytes
     * @return Decompressed bytes
     * @dev Public utility function for testing/debugging compression
     */
    function unzip(bytes memory data) external pure returns (bytes memory) {
        return abi.encodePacked(LibZip.flzDecompress(data));
    }

    /**
     * @notice Utility function to compress data using FastLZ
     * @param data Raw bytes to compress
     * @return FastLZ compressed bytes
     * @dev Public utility function for testing/debugging compression
     */
    function zip(bytes memory data) external pure returns (bytes memory) {
        return abi.encodePacked(LibZip.flzCompress(data));
    }
}
ISilhouettesStorage.sol 11 lines
// SPDX-License-Identifier: MIT

pragma solidity 0.8.30;

interface ISilhouettesStorage {
    function getTokenImageURI(uint256 tokenId) external view returns (string memory);
    function getTokenRawImage(uint256 tokenId) external view returns (bytes memory);
    function getTokenMetadata(uint256 tokenId) external view returns (string memory);
    function getTokenChunks(uint256 tokenId) external view returns (address[] memory);
    function isTokenDataSet(uint256 tokenId) external view returns (bool);
}

Read Contract

defaultTokenMetadata 0x0deaaf4c → string
getTokenChunks 0x0bac2d0a → address[]
getTokenImageURI 0x445db02a → string
getTokenMetadata 0x60316801 → string
getTokenRawImage 0x861daab9 → bytes
isTokenDataSet 0x0aebf22c → bool
loadImage 0x372df8fe → string
loadRawImage 0x5e969257 → bytes
owner 0x8da5cb5b → address
rescueLocked 0x3571ae8f → uint256
tokenData 0xb4b5b48f → string
unzip 0xdcceaea2 → bytes
zip 0xa0808b32 → bytes

Write Contract 12 functions

These functions modify contract state and require a wallet transaction to execute.

appendChunks 0xc278ede6
uint256 tokenId
bytes[] chunks
lockRescue 0x7f1f0c67
uint256 locksToSet
renounceOwnership 0x715018a6
No parameters
rescueERC1155 0x1135c5d2
address token
address to
uint256 id
uint256 amount
bytes data
rescueERC20 0xb2118a8d
address token
address to
uint256 amount
rescueERC6909 0x9311ca89
address token
address to
uint256 id
uint256 amount
rescueERC721 0x7df325e1
address token
address to
uint256 id
rescueETH 0x099a04e5
address to
uint256 amount
setDefaultTokenMetadata 0xd9072bb4
string metadata
setTokenData 0xc44c6774
uint256 tokenId
string metadata
bytes[] image
setTokenMetadata 0x510ef1de
uint256 tokenId
string metadata
transferOwnership 0xf2fde38b
address newOwner

Recent Transactions

No transactions found for this address