ETH Price: $2,524.15 (-2.77%)

Contract

0x2275d4937b6bFd3c75823744d3EfBf6c3a8dE473
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Token Holdings

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Safe Swap208018182024-09-21 22:04:479 days ago1726956287IN
0x2275d493...c3a8dE473
0 ETH0.000815376.7677378
Safe Swap207676902024-09-17 3:36:3514 days ago1726544195IN
0x2275d493...c3a8dE473
0 ETH0.000975287.08948586
Safe Swap207435972024-09-13 18:51:2317 days ago1726253483IN
0x2275d493...c3a8dE473
0 ETH0.000405422.94682972
Safe Swap207407552024-09-13 9:19:2318 days ago1726219163IN
0x2275d493...c3a8dE473
0 ETH0.00039572.87615099
Safe Swap207331392024-09-12 7:47:3519 days ago1726127255IN
0x2275d493...c3a8dE473
0 ETH0.000465343.3823934
Safe Swap207273072024-09-11 12:16:1120 days ago1726056971IN
0x2275d493...c3a8dE473
0 ETH0.000401662.9195227
Safe Swap207219942024-09-10 18:27:3520 days ago1725992855IN
0x2275d493...c3a8dE473
0 ETH0.001054167.6622326
Approve206464662024-08-31 5:28:5931 days ago1725082139IN
0x2275d493...c3a8dE473
0 ETH0.000032350.7
Safe Swap205027312024-08-11 3:37:1151 days ago1723347431IN
0x2275d493...c3a8dE473
0 ETH0.00014941.08575982
Safe Swap202660192024-07-09 2:41:1184 days ago1720492871IN
0x2275d493...c3a8dE473
0 ETH0.000338742.461748
Safe Swap202570432024-07-07 20:32:5985 days ago1720384379IN
0x2275d493...c3a8dE473
0 ETH0.000440693.20349863
Safe Swap202529112024-07-07 6:41:3586 days ago1720334495IN
0x2275d493...c3a8dE473
0 ETH0.000183111.33093592
Safe Swap202499432024-07-06 20:45:4786 days ago1720298747IN
0x2275d493...c3a8dE473
0 ETH0.000211481.53718924
Safe Swap202431232024-07-05 21:54:2387 days ago1720216463IN
0x2275d493...c3a8dE473
0 ETH0.000271821.9757286
Safe Swap202127962024-07-01 16:15:1191 days ago1719850511IN
0x2275d493...c3a8dE473
0 ETH0.001182589.81558556
Safe Swap202127802024-07-01 16:11:5991 days ago1719850319IN
0x2275d493...c3a8dE473
0 ETH0.0014746810.71968349
Approve202127762024-07-01 16:11:1191 days ago1719850271IN
0x2275d493...c3a8dE473
0 ETH0.000411888.86832022
Safe Withdraw202127672024-07-01 16:09:2391 days ago1719850163IN
0x2275d493...c3a8dE473
0 ETH0.001748499.60178905
Safe Deposit202127482024-07-01 16:05:3591 days ago1719849935IN
0x2275d493...c3a8dE473
0 ETH0.0015864911.5399004
Safe Withdraw202125062024-07-01 15:16:5992 days ago1719847019IN
0x2275d493...c3a8dE473
0 ETH0.0024292411.41188636
Safe Deposit200187272024-06-04 13:22:47119 days ago1717507367IN
0x2275d493...c3a8dE473
0 ETH0.0015992211.81440573
Safe Deposit199675422024-05-28 9:42:59126 days ago1716889379IN
0x2275d493...c3a8dE473
0 ETH0.0019695816.65441543
Safe Swap199243022024-05-22 8:42:23132 days ago1716367343IN
0x2275d493...c3a8dE473
0 ETH0.000950546.90666019
Safe Withdraw198975112024-05-18 14:45:23136 days ago1716043523IN
0x2275d493...c3a8dE473
0 ETH0.000484223.82129847
Safe Swap198950742024-05-18 6:33:59136 days ago1716014039IN
0x2275d493...c3a8dE473
0 ETH0.000503793.66185162
View all transactions

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
Goober

Compiler Version
v0.8.17+commit.8df45f5f

Optimization Enabled:
Yes with 2000000 runs

Other Settings:
default evmVersion
File 1 of 27 : Goober.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import "art-gobblers/Goo.sol";
import "art-gobblers/ArtGobblers.sol";
import {ERC20} from "solmate/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "solmate/utils/ReentrancyGuard.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import "./interfaces/IERC20Metadata.sol";
import "./math/UQ112x112.sol";
import "./interfaces/IGoober.sol";
import "./interfaces/IGooberCallee.sol";

/// @title goober_xyz
/// @author 0xAlcibiades
/// @author mevbandit
/// @author neodaoist
/// @author eth_call
/// @author goerlibot
/// @author thal0x
/// @notice Goober is an experimental Uniswap V2 and EIP-4626 flavored vault to optimize Art
/// @notice production for the decentralized art factory by Justin Roiland and Paradigm.
contract Goober is ReentrancyGuard, ERC20, IGoober {
    // We want to ensure all transfers are safe.
    using SafeTransferLib for Goo;
    using SafeTransferLib for ERC20;
    // We use this for fixed point WAD scalar math.
    using FixedPointMathLib for uint256;
    // This is the Uniswap V2 112 bit Q math, updated for Solidity 8.
    using UQ112x112 for uint224;

    /*//////////////////////////////////////////////////////////////
    // Immutable storage
    //////////////////////////////////////////////////////////////*/

    /// @notice The Goo contract.
    Goo public immutable goo;
    /// @notice The Art Gobblers NFT contract.
    ArtGobblers public immutable artGobblers;

    /// @notice The liquidity locked forever in the pool.
    uint16 private constant MINIMUM_LIQUIDITY = 1e3;
    /// @notice A scalar for scaling up and down to basis points.
    uint16 private constant BPS_SCALAR = 1e4;
    /// @notice The management fee in basis points, charged on deposits.
    uint16 public constant MANAGEMENT_FEE_BPS = 200;
    /// @notice The performance fee in basis points, taken in the form
    /// @notice of dilution on the growth of sqrt(gooBalance * gobblerMult),
    /// @notice
    uint16 public constant PERFORMANCE_FEE_BPS = 1e3;
    /// @notice The average multiplier of a newly minted gobbler.
    /// @notice 7.3294 = weighted avg. multiplier from mint probabilities,
    /// @notice derived from: ((6*3057) + (7*2621) + (8*2293) + (9*2029)) / 10000.
    uint32 private constant AVERAGE_MULT_BPS = 73294;

    /*//////////////////////////////////////////////////////////////
    // Mutable storage
    //////////////////////////////////////////////////////////////*/

    // Access control
    /// @notice This is the "admin" address and also where management and performance fees accrue.
    address public feeTo;
    /// @notice This is a privileged actor with the ability to mint gobblers when the pool price is low enough.
    address public minter;

    /// @notice Price oracle accumulator for goo.
    uint256 public priceGooCumulativeLast;
    /// @notice Price oracle accumulator for gobbler multipliers.
    uint256 public priceGobblerCumulativeLast;

    /// @notice K, as of immediately after the most recent liquidity event.
    uint112 public kLast;
    /// @notice A counter for debt accrued against performance fees during the temporary decreases in K
    /// @notice after mints by the minter, before gobblers' multipliers are revealed.
    uint112 public kDebt;

    /// @notice Last block timestamp
    /// @dev Yes, the oracle accumulators will reset in 2036.
    uint32 public blockTimestampLast; // uses single storage slot, accessible via getReserves

    /// @notice Flagged NFTs cannot be deposited or swapped in.
    mapping(uint256 => bool) public flagged;

    /*//////////////////////////////////////////////////////////////
    // Constructor
    //////////////////////////////////////////////////////////////*/

    /// @notice Deploys the goober contract.
    /// @param _gobblersAddress - The address of the Art Gobblers contract.
    /// @param _gooAddress - The address of the Goo contract/token.
    /// @param _feeTo - The admin and address to accrue fees to.
    /// @param _minter - The address able to mint gobblers to the pool.
    /// @notice The minter is able to mint using pool assets based on conditions defined in the mintGobbler function.
    constructor(address _gobblersAddress, address _gooAddress, address _feeTo, address _minter)
        ERC20("Goober", "GBR", 18)
    {
        feeTo = _feeTo;
        minter = _minter;
        artGobblers = ArtGobblers(_gobblersAddress);
        goo = Goo(_gooAddress);
    }

    /*//////////////////////////////////////////////////////////////
    // Modifiers
    //////////////////////////////////////////////////////////////*/

    /// @notice This modifier restricts function access to the feeTo address.
    modifier onlyFeeTo() {
        if (msg.sender != feeTo) {
            revert AccessControlViolation(msg.sender, feeTo);
        }
        _;
    }

    /// @notice This modifier restricts function access to the minter address.
    modifier onlyMinter() {
        if (msg.sender != minter) {
            revert AccessControlViolation(msg.sender, minter);
        }
        _;
    }

    /// @notice This modifier ensures the transaction is included before a specified deadline.
    /// @param deadline - Unix timestamp after which the transaction will revert.
    modifier ensure(uint256 deadline) {
        if (block.timestamp > deadline) {
            revert Expired(block.timestamp, deadline);
        }
        _;
    }

    /*//////////////////////////////////////////////////////////////
    // Internal: Non-Mutating
    //////////////////////////////////////////////////////////////*/

    /// @notice Calculates it is within the interest of our pool to mint more Gobblers
    /// @notice based on current pool and auction conditions.
    function _shouldMint(uint256 _gooBalance, uint256 _gobblerBalance, uint256 _auctionPrice)
        internal
        pure
        returns (bool mint, uint256 auctionPricePerMult, uint256 poolPricePerMult)
    {
        if (_gooBalance == 0 || _gobblerBalance == 0) {
            revert InsufficientLiquidity(_gooBalance, _gobblerBalance);
        } else if (_auctionPrice == 0) {
            // Unlikely, but avoids divide by zero below.
            mint = true;
        } else {
            if (_gooBalance > _auctionPrice) {
                auctionPricePerMult = (_auctionPrice * BPS_SCALAR) / AVERAGE_MULT_BPS;
                poolPricePerMult = (_gooBalance / _gobblerBalance);
                mint = poolPricePerMult > auctionPricePerMult;
            }
        }
    }

    /// @dev Internal swap calculations from Uniswap V2 modified to return an integer error term.
    function _swapCalculations(
        uint256 _gooReserve,
        uint256 _gobblerReserve,
        uint256 _gooBalance,
        uint256 _gobblerBalance,
        uint256 gooOut,
        uint256 multOut,
        bool revertInsufficient
    ) internal pure returns (int256 erroneousGoo, uint256 amount0In, uint256 amount1In) {
        erroneousGoo = 0;
        amount0In = _gooBalance > _gooReserve - gooOut ? _gooBalance - (_gooReserve - gooOut) : 0;
        amount1In = _gobblerBalance > _gobblerReserve - multOut ? _gobblerBalance - (_gobblerReserve - multOut) : 0;
        if (!(amount0In > 0 || amount1In > 0)) {
            revert InsufficientInputAmount(amount0In, amount1In);
        }
        {
            uint256 balance0Adjusted = (_gooBalance * 1000) - (amount0In * 3);
            uint256 balance1Adjusted = (_gobblerBalance * 1000) - (amount1In * 3);
            uint256 adjustedBalanceK = ((balance0Adjusted * balance1Adjusted));
            uint256 expectedK = ((_gooReserve * _gobblerReserve) * 1000 ** 2);

            if (adjustedBalanceK < expectedK) {
                uint256 error = FixedPointMathLib.mulWadUp(
                    FixedPointMathLib.divWadUp(
                        (
                            FixedPointMathLib.mulWadUp(FixedPointMathLib.divWadUp(expectedK, balance1Adjusted), 1)
                                - balance0Adjusted
                        ),
                        997
                    ),
                    1
                );
                if (revertInsufficient) {
                    revert InsufficientGoo(error, adjustedBalanceK, expectedK);
                }
                erroneousGoo += int256(error);
            } else if (adjustedBalanceK > expectedK) {
                erroneousGoo -= int256(
                    FixedPointMathLib.mulWadDown(
                        FixedPointMathLib.divWadDown(
                            (
                                balance0Adjusted
                                    - FixedPointMathLib.mulWadUp(FixedPointMathLib.divWadUp(expectedK, balance1Adjusted), 1)
                            ),
                            1000
                        ),
                        1
                    )
                );
            }
            // Otherwise return 0.
        }
    }

    /// @notice Returns various calculations about the value of K.
    function _kCalculations(uint256 _gooBalance, uint256 _gobblerBalance, uint256 _kLast, uint256 _kDebt, bool _roundUp)
        internal
        pure
        returns (uint256 _k, uint256 _kChange, bool _kChangeSign, uint256 _kDelta, uint256 _kDebtChange)
    {
        // Get the present value of K.
        _k = FixedPointMathLib.sqrt(_gooBalance * _gobblerBalance);
        // We don't want to allow the pool to be decommed, ever.
        if (_k == 0) {
            revert MustLeaveLiquidity(_gooBalance, _gobblerBalance);
        }
        // Set delta and debt change to zero.
        _kDelta = 0;
        _kDebtChange = 0;
        // Did K increase or decrease?
        _kChangeSign = _k > _kLast;
        // Get the gross change in K as a numeric.
        _kChange = _kChangeSign ? _k - _kLast : _kLast - _k;
        // We can't do change math on a fresh pool.
        if (_kLast > 0) {
            // If K went up,
            if (_kChangeSign) {
                // let's offset the debt first if it exists;
                if (_kDebt > 0) {
                    if (_kChange <= _kDebt) {
                        _kDebtChange += _kChange;
                        _kChange = 0;
                    } else {
                        _kDebtChange += _kDebt;
                        _kChange -= _kDebt;
                    }
                }
            }
            // then we can calculate the delta.
            if (_roundUp) {
                _kDelta = FixedPointMathLib.divWadUp(_kChange, _kLast);
            } else {
                _kDelta = FixedPointMathLib.divWadDown(_kChange, _kLast);
            }
        } else {
            // If kLast -> k is 0 -> n, then the delta is 100%.
            _kDelta = FixedPointMathLib.divWadUp(1, 1);
        }
    }

    /// @notice Returns the management fee given an amount of new fractions created on deposit.
    /// @param fractions New fractions issued for a deposit.
    function _previewManagementFee(uint256 fractions) internal pure returns (uint256 fee) {
        fee = fractions * MANAGEMENT_FEE_BPS / BPS_SCALAR;
    }

    /// @notice Returns a preview of the performance fee.
    /// @param _gooBalance - The Goo balance to simulate with.
    /// @param _gobblerBalance - The Gobbler balance to simulate with.
    function _previewPerformanceFee(uint256 _gooBalance, uint256 _gobblerBalance)
        internal
        view
        returns (uint256 fee, uint256 kDebtChange, uint256 kDelta)
    {
        // No K, no fee.
        uint112 _kLast = kLast;
        uint112 _kDebt = kDebt;
        fee = 0;
        kDebtChange = 0;
        kDelta = 0;
        // If kLast was at 0, then we won't accrue a fee yet, as the pool is uninitialized.
        if (_kLast > 0) {
            (, uint256 _kChange, bool _kChangeSign, uint256 _kDelta, uint256 _kDebtChange) =
                _kCalculations(_gooBalance, _gobblerBalance, _kLast, _kDebt, false);
            // Then, determine a fee on any remainder after offsetting outstanding debt.
            if (_kChange > 0 && _kChangeSign) {
                // Calculate the fee as a portion of the the growth of total supply as determined by kDelta.
                fee = FixedPointMathLib.mulWadDown(totalSupply, _kDelta) * PERFORMANCE_FEE_BPS / BPS_SCALAR;
                // Update kDelta return value.
                kDelta = uint112(_kDelta);
                // Update kDebtChange return value.
                kDebtChange = uint112(_kDebtChange);
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
    // Internal: Mutating
    //////////////////////////////////////////////////////////////*/

    /// @dev Update reserves and, on the first call per block, price accumulators.
    /// @param _gooBalance - The new Goo balance.
    /// @param _gobblerBalance - The new Gobbler multiplier.
    /// @param _gooReserve - The previous Goo reserve.
    /// @param _gobblerReserve - The previous Gobbler multiplier.
    function _update(
        uint256 _gooBalance,
        uint256 _gobblerBalance,
        uint256 _gooReserve,
        uint256 _gobblerReserve,
        bool recordDebt,
        bool updateK
    ) internal {
        /// @dev The accumulators will reset in 2036 due to modulo.
        //slither-disable-next-line weak-prng
        uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);

        uint32 timeElapsed;
        unchecked {
            // The time elapsed since the last update.
            timeElapsed = blockTimestamp - blockTimestampLast; // Overflow is desired.
        }

        // These are accumulators which can be used for a Goo/Gobbler mult TWAP.
        (uint112 castGooReserve, uint112 castGobblerReserve) = (uint112(_gooReserve), uint112(_gobblerReserve));
        if (timeElapsed > 0 && _gooReserve != 0 && _gobblerReserve != 0) {
            unchecked {
                // * Never overflows, and + overflow is desired.
                priceGooCumulativeLast +=
                    uint256(UQ112x112.encode(uint112(castGooReserve)).uqdiv(castGobblerReserve)) * timeElapsed;
                priceGobblerCumulativeLast +=
                    uint256(UQ112x112.encode(uint112(castGobblerReserve)).uqdiv(uint112(castGooReserve))) * timeElapsed;
            }
        }

        // Update the last update.
        blockTimestampLast = blockTimestamp;

        /// @dev We don't store reserves here as they were already stored in other contracts and there was no
        // need to duplicate the state changes.

        // Do we need to update historic K values?
        if (updateK || recordDebt) {
            // Get the present K.
            uint112 _k = uint112(FixedPointMathLib.sqrt(_gooBalance * _gobblerBalance));

            // Read the last K from storage.
            uint112 _kLast = kLast;

            // If K decreased, record the debt.
            if ((_k < _kLast) && recordDebt) {
                kDebt += _kLast - _k;
            }

            if (updateK) {
                // Update historic K.
                kLast = _k;
            }
        }

        // Emit the reserves which can be used to chart the state of the pool.
        emit Sync(_gooBalance, _gobblerBalance);
    }

    /// @notice Accrues the performance fee on the growth of K if any, offset by kDebt.
    /// @param _gooBalance - The balance of Goo to use in calculating the growth of K.
    /// @param _gobblerBalanceMult - The balance of Gobbler mult to use in calculating the growth of K.
    function _performanceFee(uint256 _gooBalance, uint256 _gobblerBalanceMult) internal returns (uint256) {
        (uint256 fee, uint256 kDebtChange, uint256 deltaK) = _previewPerformanceFee(_gooBalance, _gobblerBalanceMult);
        if (kDebtChange > 0) {
            kDebt -= uint112(kDebtChange);
        }
        if (fee > 0) {
            _mint(feeTo, fee);
            // Emit info about the fees, and the growth of K.
            emit FeesAccrued(feeTo, fee, true, deltaK);
        }
        return fee;
    }

    /// @notice Mints and returns the management fee given an amount of new fractions created on deposit.
    /// @param fractions - New fractions created for a deposit.
    /// @return fee - The managment fee assesed.
    function _managementFee(uint256 fractions) internal returns (uint256 fee) {
        fee = _previewManagementFee(fractions);
        _mint(feeTo, fee);
        // _deltaK is 0 here because there isn't actually growth in K.
        emit FeesAccrued(feeTo, fee, false, 0);
    }

    /*//////////////////////////////////////////////////////////////
    // External: Non Mutating
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IERC721Receiver
    /// @notice Handles deposits of Art Gobblers with an on receive hook, verifying characteristics.
    /// @dev We don't accept non Art Gobbler NFTs, flagged Gobblers, or unrevealed Gobblers.
    function onERC721Received(address, address, uint256 tokenId, bytes calldata) external view returns (bytes4) {
        /// @dev We only want Art Gobblers NFTs.
        if (msg.sender != address(artGobblers)) {
            revert InvalidNFT();
        }
        /// @dev Revert on flagged NFTs.
        if (flagged[tokenId] == true) {
            revert InvalidNFT();
        }
        /// @dev We want to make sure the Gobblers we are getting are revealed.
        uint256 gobMult = artGobblers.getGobblerEmissionMultiple(tokenId);
        if (gobMult < 6) {
            revert InvalidMultiplier(tokenId);
        }
        return IERC721Receiver.onERC721Received.selector;
    }

    /// @inheritdoc IGoober
    function totalAssets() public view returns (uint256 gooTokens, uint256 gobblerMult) {
        gooTokens = artGobblers.gooBalance(address(this));
        gobblerMult = artGobblers.getUserEmissionMultiple(address(this));
    }

    /// @inheritdoc IGoober
    function convertToFractions(uint256 gooTokens, uint256 gobblerMult) external view returns (uint256 fractions) {
        uint256 _totalSupply = totalSupply;
        uint256 kInput = FixedPointMathLib.sqrt(gooTokens * gobblerMult);
        if (_totalSupply > 0) {
            (uint256 gooBalance, uint256 gobblerMultBalance) = totalAssets();
            uint256 kBalance = FixedPointMathLib.sqrt(gooBalance * gobblerMultBalance);
            uint256 kDelta = FixedPointMathLib.divWadDown(kInput, kBalance);
            fractions = FixedPointMathLib.mulWadDown(_totalSupply, kDelta);
        } else {
            fractions = kInput;
        }
    }

    /// @inheritdoc IGoober
    function convertToAssets(uint256 fractions)
        external
        view
        virtual
        returns (uint256 gooTokens, uint256 gobblerMult)
    {
        gooTokens = 0;
        gobblerMult = 0;
        uint256 _totalSupply = totalSupply;
        if (_totalSupply > 0) {
            (gooTokens, gobblerMult) = totalAssets();
            gooTokens = fractions.mulDivDown(gooTokens, _totalSupply);
            gobblerMult = fractions.mulDivDown(gobblerMult, _totalSupply);
        }
    }

    /// @inheritdoc IGoober
    function getReserves()
        public
        view
        returns (uint256 _gooReserve, uint256 _gobblerReserve, uint32 _blockTimestampLast)
    {
        _gooReserve = artGobblers.gooBalance(address(this));
        _gobblerReserve = artGobblers.getUserEmissionMultiple(address(this));
        _blockTimestampLast = blockTimestampLast;
    }

    /// @inheritdoc IGoober
    function previewDeposit(uint256[] calldata gobblers, uint256 gooTokens) external view returns (uint256 fractions) {
        // Collect a virtual performance fee.
        (uint256 _gooReserve, uint256 _gobblerReserveMult,) = getReserves();
        (uint256 pFee,,) = _previewPerformanceFee(_gooReserve, _gobblerReserveMult);
        // Increment virtual total supply by performance fee.
        uint256 _totalSupply = pFee + totalSupply;
        // Simulate transfers.
        uint256 _gooBalance = gooTokens + _gooReserve;
        uint256 _gobblerBalanceMult = _gobblerReserveMult;
        for (uint256 i = 0; i < gobblers.length; i++) {
            _gobblerBalanceMult += artGobblers.getGobblerEmissionMultiple(gobblers[i]);
        }
        // Calculate the fractions to create based on the changes in K.
        (uint256 _k,,, uint256 _kDelta,) = _kCalculations(
            _gooBalance, _gobblerBalanceMult, FixedPointMathLib.sqrt(_gooReserve * _gobblerReserveMult), 0, true
        );
        if (_totalSupply == 0) {
            // We want to start the fractions at the right order of magnitude at init, so
            // we scale this by 1e9 to simulate 2 ERC20s, because Gobbler mult are integers
            // rather than 1e18 ERC20s from the Uni V2 design.
            fractions = _k * 1e9 - MINIMUM_LIQUIDITY;
        } else {
            fractions = FixedPointMathLib.mulWadDown(_totalSupply, _kDelta);
        }
        if (fractions == 0) {
            revert InsufficientLiquidityDeposited();
        }
        // Simulate management fee and return preview.
        fractions -= _previewManagementFee(fractions);
    }

    /// @inheritdoc IGoober
    function previewWithdraw(uint256[] calldata gobblers, uint256 gooTokens)
        external
        view
        returns (uint256 fractions)
    {
        // Collect a virtual performance fee.
        (uint256 _gooReserve, uint256 _gobblerReserveMult,) = getReserves();
        uint256 _totalSupply = totalSupply;
        (uint256 pFee,,) = _previewPerformanceFee(_gooReserve, _gobblerReserveMult);
        // Increment virtual total supply.
        _totalSupply += pFee;
        // Simulate transfers.
        uint256 _gooBalance = _gooReserve - gooTokens;
        uint256 _gobblerBalanceMult = _gobblerReserveMult;
        uint256 gobblerMult;
        for (uint256 i = 0; i < gobblers.length; i++) {
            if (artGobblers.ownerOf(gobblers[i]) != address(this)) {
                revert InvalidNFT();
            }
            gobblerMult = artGobblers.getGobblerEmissionMultiple(gobblers[i]);
            if (gobblerMult < 6) {
                revert InvalidMultiplier(gobblers[i]);
            }
            _gobblerBalanceMult -= gobblerMult;
        }
        uint256 _gobblerAmountMult = _gobblerReserveMult - _gobblerBalanceMult;
        if (!(_gobblerAmountMult > 0 || gooTokens > 0)) {
            revert InsufficientLiquidityWithdrawn();
        }
        {
            // Calculate the fractions which will be destroyed based on the changes in K.
            (,,, uint256 _kDelta,) = _kCalculations(
                _gooBalance, _gobblerBalanceMult, FixedPointMathLib.sqrt(_gooReserve * _gobblerReserveMult), 0, true
            );
            // Update fractions for return.
            fractions = FixedPointMathLib.mulWadUp(_totalSupply, _kDelta);
        }
    }

    /// @inheritdoc IGoober
    function previewSwap(uint256[] calldata gobblersIn, uint256 gooIn, uint256[] calldata gobblersOut, uint256 gooOut)
        public
        view
        returns (int256 erroneousGoo)
    {
        (uint256 _gooReserve, uint256 _gobblerReserve,) = getReserves();
        // Simulate the transfers out.
        uint256 _gooBalance = _gooReserve - gooOut;
        uint256 _gobblerBalance = _gobblerReserve;
        uint256 multOut = 0;
        uint256 gobblerMult;
        for (uint256 i = 0; i < gobblersOut.length; i++) {
            if (artGobblers.ownerOf(gobblersOut[i]) != address(this)) {
                revert InvalidNFT();
            }
            gobblerMult = artGobblers.getGobblerEmissionMultiple(gobblersOut[i]);
            if (gobblerMult < 6) {
                revert InvalidMultiplier(gobblersOut[i]);
            }
            _gobblerBalance -= gobblerMult;
            multOut += gobblerMult;
        }
        // Simulate the transfers in.
        _gooBalance += gooIn;
        for (uint256 i = 0; i < gobblersIn.length; i++) {
            gobblerMult = artGobblers.getGobblerEmissionMultiple(gobblersIn[i]);
            if (gobblerMult < 6) {
                revert InvalidMultiplier(gobblersIn[i]);
            }
            _gobblerBalance += gobblerMult;
        }
        // Run swap calculations and return error term.
        (erroneousGoo,,) =
            _swapCalculations(_gooReserve, _gobblerReserve, _gooBalance, _gobblerBalance, gooOut, multOut, false);
    }

    /*//////////////////////////////////////////////////////////////
    // External: Mutating, Restricted Access
    //////////////////////////////////////////////////////////////*/

    // Access Control

    /// @inheritdoc IGoober
    function setFeeTo(address newFeeTo) external onlyFeeTo {
        if (newFeeTo == address(0)) {
            revert InvalidAddress(newFeeTo);
        }
        feeTo = newFeeTo;
    }

    /// @inheritdoc IGoober
    function setMinter(address newMinter) external onlyFeeTo {
        if (newMinter == address(0)) {
            revert InvalidAddress(newMinter);
        }
        minter = newMinter;
    }

    // Other Privileged Functions

    /// @inheritdoc IGoober
    function mintGobbler() external nonReentrant onlyMinter {
        /// @dev Restricted to onlyMinter to prevent Goo price manipulation.
        /// @dev Non-reentrant in case onlyMinter address/keeper is compromised.

        // Get the mint price.
        uint256 mintPrice = artGobblers.gobblerPrice();

        // Get the reserves directly to save gas.
        uint256 gooReserve = artGobblers.gooBalance(address(this));
        uint256 gobblerReserve = artGobblers.getUserEmissionMultiple(address(this));

        // Set an internal balance counter for Goo.
        uint256 gooBalance = gooReserve;

        // Should we mint?
        (bool mint, uint256 auctionPricePerMult, uint256 poolPricePerMult) =
            _shouldMint(gooBalance, gobblerReserve, mintPrice);

        // We revert to tell the minter if it's calculations are off.
        if (mint == false) {
            revert AuctionPriceTooHigh(auctionPricePerMult, poolPricePerMult);
        }

        // Mint Gobblers to the pool when our Goo per mult > (VRGDA) auction Goo per mult.
        while (mint) {
            // Mint a new Gobbler.
            // slither-disable-next-line unused-return
            artGobblers.mintFromGoo(mintPrice, true);

            // _shouldMint already prevents an overflow here.
            gooBalance -= mintPrice;

            // Emit info about the mint for off-chain analysis.
            emit VaultMint(msg.sender, auctionPricePerMult, poolPricePerMult, mintPrice);

            // Get the new mint price.
            mintPrice = artGobblers.gobblerPrice();

            // Should we mint again?
            (mint, auctionPricePerMult, poolPricePerMult) = _shouldMint(gooBalance, gobblerReserve, mintPrice);
        }

        // Update accumulators, kLast, kDebt.
        _update(gooBalance, gobblerReserve, gooReserve, gobblerReserve, true, true);
    }

    /// @inheritdoc IGoober
    function skim(address erc20) external nonReentrant onlyFeeTo {
        /// @dev Contract should never hold ERC20 tokens (only virtual GOO).
        uint256 contractBalance = ERC20(erc20).balanceOf(address(this));
        //slither-disable-next-line dangerous-strict-equalities
        if (contractBalance == 0) {
            revert NoSkim();
        }
        // Transfer the excess goo to the admin for handling.
        ERC20(erc20).safeTransfer(msg.sender, contractBalance);
    }

    /// @inheritdoc IGoober
    function flagGobbler(uint256 tokenId, bool _flagged) external onlyFeeTo {
        flagged[tokenId] = _flagged;
    }

    /*//////////////////////////////////////////////////////////////
    // External: Mutating, Unrestricted
    //////////////////////////////////////////////////////////////*/

    /// @inheritdoc IGoober
    function deposit(uint256[] calldata gobblers, uint256 gooTokens, address receiver)
        public
        nonReentrant
        returns (uint256 fractions)
    {
        // Get reserve balances before they are updated from deposit transfers.
        (uint256 _gooReserve, uint256 _gobblerReserveMult,) = getReserves();

        // Assess performance fee since last transaction.
        _performanceFee(_gooReserve, _gobblerReserveMult);

        // Transfer goo if any.
        if (gooTokens > 0) {
            goo.safeTransferFrom(msg.sender, address(this), gooTokens);
            artGobblers.addGoo(gooTokens);
        }

        // Transfer gobblers if any.
        for (uint256 i = 0; i < gobblers.length; i++) {
            artGobblers.safeTransferFrom(msg.sender, address(this), gobblers[i]);
        }

        // Get the new reserves after transfers in.
        (uint256 _gooBalance, uint256 _gobblerBalanceMult,) = getReserves();
        {
            // Check the total token supply.
            uint256 _totalSupply = totalSupply;

            // Calculate issuance.
            uint256 _kLast = FixedPointMathLib.sqrt(_gooReserve * _gobblerReserveMult);
            // Calculate the fractions to create based on the changes in K.
            (uint256 _k,,, uint256 _kDelta,) = _kCalculations(_gooBalance, _gobblerBalanceMult, _kLast, 0, true);
            if (_totalSupply == 0) {
                // We want to start the fractions at the right order of magnitude at init, so
                // we scale this by 1e9 to simulate 2 ERC20s, because Gobbler mult are integers
                // rather than 1e18 ERC20s from the Uni V2 design.
                fractions = _k * 1e9 - MINIMUM_LIQUIDITY;
            } else {
                fractions = FixedPointMathLib.mulWadDown(_totalSupply, _kDelta);
            }
            if (fractions == 0) {
                revert InsufficientLiquidityDeposited();
            }
        }

        // Mint fractions less management fee to depositor.
        fractions -= _managementFee(fractions);
        _mint(receiver, fractions);

        // Update kLast and accumulators.
        _update(_gooBalance, _gobblerBalanceMult, _gooReserve, _gobblerReserveMult, false, true);

        emit Deposit(msg.sender, receiver, gobblers, gooTokens, fractions);
    }

    /// @inheritdoc IGoober
    function safeDeposit(
        uint256[] calldata gobblers,
        uint256 gooTokens,
        address receiver,
        uint256 minFractionsOut,
        uint256 deadline
    ) external ensure(deadline) returns (uint256 fractions) {
        fractions = deposit(gobblers, gooTokens, receiver);

        if (fractions < minFractionsOut) {
            revert MintBelowLimit();
        }
    }

    /// @inheritdoc IGoober
    function withdraw(uint256[] calldata gobblers, uint256 gooTokens, address receiver, address owner)
        public
        nonReentrant
        returns (uint256 fractions)
    {
        // Get the starting reserves.
        (uint256 _gooReserve, uint256 _gobblerReserveMult,) = getReserves();
        (uint256 _gooBalance, uint256 _gobblerBalanceMult) = (_gooReserve, _gobblerReserveMult);

        // Assess performance fee since the last update of K.
        _performanceFee(_gooReserve, _gobblerReserveMult);

        // Optimistically transfer Goo, if any.
        if (gooTokens > 0) {
            artGobblers.removeGoo(gooTokens);
            goo.safeTransfer(receiver, gooTokens);
            _gooBalance -= gooTokens;
        }

        // Optimistically transfer Gobblers, if any.
        uint256 gobblerMult;
        for (uint256 i = 0; i < gobblers.length; i++) {
            gobblerMult = artGobblers.getGobblerEmissionMultiple(gobblers[i]);
            if (gobblerMult < 6) {
                revert InvalidMultiplier(gobblers[i]);
            }
            artGobblers.transferFrom(address(this), receiver, gobblers[i]);
            _gobblerBalanceMult -= gobblerMult;
        }

        // Measure the change in Gobbler mult.
        uint256 _gobblerAmountMult = _gobblerReserveMult - _gobblerBalanceMult;

        if (!(_gobblerAmountMult > 0 || gooTokens > 0)) {
            revert InsufficientLiquidityWithdrawn();
        }

        {
            // Calculate the fractions to destroy based on the changes in K.
            (,,, uint256 _kDelta,) = _kCalculations(
                _gooBalance, _gobblerBalanceMult, FixedPointMathLib.sqrt(_gooReserve * _gobblerReserveMult), 0, true
            );
            uint256 _totalSupply = totalSupply;
            fractions = FixedPointMathLib.mulWadUp(_totalSupply, _kDelta);
        }
        // If we are withdrawing on behalf of someone else, we need to check that they have approved us to do so.
        if (msg.sender != owner) {
            uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals.

            // Check that we can withdraw the requested amount of liquidity.
            if (allowed < fractions) {
                revert InsufficientAllowance();
            }

            if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - fractions;
        }

        // Destroy the fractions from owner.
        _burn(owner, fractions);

        // Update the reserves.
        _update(_gooBalance, _gobblerBalanceMult, _gooReserve, _gobblerReserveMult, false, true);

        emit Withdraw(msg.sender, receiver, owner, gobblers, gooTokens, fractions);
    }

    /// @inheritdoc IGoober
    function safeWithdraw(
        uint256[] calldata gobblers,
        uint256 gooTokens,
        address receiver,
        address owner,
        uint256 maxFractionsIn,
        uint256 deadline
    ) external ensure(deadline) returns (uint256 fractions) {
        fractions = withdraw(gobblers, gooTokens, receiver, owner);

        if (fractions > maxFractionsIn) {
            revert BurnAboveLimit();
        }
    }

    /// @inheritdoc IGoober
    function swap(
        uint256[] calldata gobblersIn,
        uint256 gooIn,
        uint256[] calldata gobblersOut,
        uint256 gooOut,
        address receiver,
        bytes calldata data
    ) public nonReentrant returns (int256) {
        if (!(gooOut > 0 || gobblersOut.length > 0)) {
            revert InsufficientOutputAmount(gooOut, gobblersOut.length);
        }

        if (receiver == address(goo) || receiver == address(artGobblers)) {
            revert InvalidReceiver(receiver);
        }

        // Intermediary struct so that the stack doesn't get too deep.
        SwapData memory internalData = SwapData({
            gooReserve: 0,
            gobblerReserve: 0,
            gooBalance: 0,
            gobblerBalance: 0,
            multOut: 0,
            amount0In: 0,
            amount1In: 0,
            erroneousGoo: 0
        });

        (internalData.gooReserve, internalData.gobblerReserve,) = getReserves();

        // Transfer out.

        // Optimistically transfer Goo, if any.
        if (gooOut > 0) {
            // Will underflow and revert if we don't have enough Goo, by design.
            artGobblers.removeGoo(gooOut);
            goo.safeTransfer(receiver, gooOut);
        }

        // Optimistically transfer Gobblers, if any.
        if (gobblersOut.length > 0) {
            for (uint256 i = 0; i < gobblersOut.length; i++) {
                uint256 gobblerMult = artGobblers.getGobblerEmissionMultiple(gobblersOut[i]);
                if (gobblerMult < 6) {
                    revert InvalidMultiplier(gobblersOut[i]);
                }
                internalData.multOut += gobblerMult;
                artGobblers.transferFrom(address(this), receiver, gobblersOut[i]);
            }
        }

        /// @dev Flash loan call out.
        /// @dev Unlike Uni V2, we only need to send the data,
        /// @dev because token transfers in are pulled below.
        if (data.length > 0) IGooberCallee(receiver).gooberCall(data);

        // Transfer in.

        // Transfer in Goo, if any.
        if (gooIn > 0) {
            goo.safeTransferFrom(msg.sender, address(this), gooIn);
            artGobblers.addGoo(gooIn);
        }

        // Transfer in Gobblers, if any
        for (uint256 i = 0; i < gobblersIn.length; i++) {
            artGobblers.safeTransferFrom(msg.sender, address(this), gobblersIn[i]);
        }

        // Get updated balances.
        (internalData.gooBalance, internalData.gobblerBalance,) = getReserves();

        // Perform swap computation.
        (internalData.erroneousGoo, internalData.amount0In, internalData.amount1In) = _swapCalculations(
            internalData.gooReserve,
            internalData.gobblerReserve,
            internalData.gooBalance,
            internalData.gobblerBalance,
            gooOut,
            internalData.multOut,
            true
        );

        // Update oracle.
        _update(
            internalData.gooBalance,
            internalData.gobblerBalance,
            internalData.gooReserve,
            internalData.gobblerReserve,
            false,
            false
        );

        emit Swap(msg.sender, receiver, internalData.amount0In, internalData.amount1In, gooOut, internalData.multOut);

        return internalData.erroneousGoo;
    }

    /// @inheritdoc IGoober
    function safeSwap(
        uint256 erroneousGooAbs,
        uint256 deadline,
        uint256[] calldata gobblersIn,
        uint256 gooIn,
        uint256[] calldata gobblersOut,
        uint256 gooOut,
        address receiver,
        bytes calldata data
    ) external ensure(deadline) returns (int256 erroneousGoo) {
        erroneousGoo = previewSwap(gobblersIn, gooIn, gobblersOut, gooOut);
        if (erroneousGoo < 0) {
            uint256 additionalGooOut = uint256(-erroneousGoo);
            if (additionalGooOut > erroneousGooAbs) {
                revert ExcessiveErroneousGoo(additionalGooOut, erroneousGooAbs);
            }
            gooOut += additionalGooOut;
        } else if (erroneousGoo > 0) {
            uint256 additionalGooIn = uint256(erroneousGoo);
            if (additionalGooIn > erroneousGooAbs) {
                revert ExcessiveErroneousGoo(additionalGooIn, erroneousGooAbs);
            }
            gooIn += additionalGooIn;
        }

        erroneousGoo = swap(gobblersIn, gooIn, gobblersOut, gooOut, receiver, data);
    }
}

File 2 of 27 : LogisticToLinearVRGDA.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {unsafeWadDiv} from "solmate/utils/SignedWadMath.sol";

import {VRGDA} from "./VRGDA.sol";
import {LogisticVRGDA} from "./LogisticVRGDA.sol";

/// @title Logistic To Linear Variable Rate Gradual Dutch Auction
/// @author transmissions11 <[email protected]>
/// @author FrankieIsLost <[email protected]>
/// @notice VRGDA with a piecewise logistic and linear issuance curve.
abstract contract LogisticToLinearVRGDA is LogisticVRGDA {
    /*//////////////////////////////////////////////////////////////
                           PRICING PARAMETERS
    //////////////////////////////////////////////////////////////*/

    /// @dev The number of tokens that must be sold for the switch to occur.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal immutable soldBySwitch;

    /// @dev The time soldBySwitch tokens were targeted to sell by.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal immutable switchTime;

    /// @dev The total number of tokens to target selling every full unit of time.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal immutable perTimeUnit;

    /// @notice Sets pricing parameters for the VRGDA.
    /// @param _targetPrice The target price for a token if sold on pace, scaled by 1e18.
    /// @param _priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18.
    /// @param _logisticAsymptote The asymptote (minus 1) of the pre-switch logistic curve, scaled by 1e18.
    /// @param _timeScale The steepness of the pre-switch logistic curve, scaled by 1e18.
    /// @param _soldBySwitch The number of tokens that must be sold for the switch to occur.
    /// @param _switchTime The time soldBySwitch tokens were targeted to sell by, scaled by 1e18.
    /// @param _perTimeUnit The number of tokens to target selling in 1 full unit of time, scaled by 1e18.
    constructor(
        int256 _targetPrice,
        int256 _priceDecayPercent,
        int256 _logisticAsymptote,
        int256 _timeScale,
        int256 _soldBySwitch,
        int256 _switchTime,
        int256 _perTimeUnit
    ) LogisticVRGDA(_targetPrice, _priceDecayPercent, _logisticAsymptote, _timeScale) {
        soldBySwitch = _soldBySwitch;

        switchTime = _switchTime;

        perTimeUnit = _perTimeUnit;
    }

    /*//////////////////////////////////////////////////////////////
                              PRICING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by.
    /// @param sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for.
    /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is
    /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins.
    function getTargetSaleTime(int256 sold) public view virtual override returns (int256) {
        // If we've not yet reached the number of sales required for the switch
        // to occur, we'll continue using the standard logistic VRGDA schedule.
        if (sold < soldBySwitch) return LogisticVRGDA.getTargetSaleTime(sold);

        unchecked {
            return unsafeWadDiv(sold - soldBySwitch, perTimeUnit) + switchTime;
        }
    }
}

File 3 of 27 : LogisticVRGDA.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {wadLn, unsafeDiv, unsafeWadDiv} from "solmate/utils/SignedWadMath.sol";

import {VRGDA} from "./VRGDA.sol";

/// @title Logistic Variable Rate Gradual Dutch Auction
/// @author transmissions11 <[email protected]>
/// @author FrankieIsLost <[email protected]>
/// @notice VRGDA with a logistic issuance curve.
abstract contract LogisticVRGDA is VRGDA {
    /*//////////////////////////////////////////////////////////////
                           PRICING PARAMETERS
    //////////////////////////////////////////////////////////////*/

    /// @dev The maximum number of tokens of tokens to sell + 1. We add
    /// 1 because the logistic function will never fully reach its limit.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal immutable logisticLimit;

    /// @dev The maximum number of tokens of tokens to sell + 1 multiplied
    /// by 2. We could compute it on the fly each time but this saves gas.
    /// @dev Represented as a 36 decimal fixed point number.
    int256 internal immutable logisticLimitDoubled;

    /// @dev Time scale controls the steepness of the logistic curve,
    /// which affects how quickly we will reach the curve's asymptote.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal immutable timeScale;

    /// @notice Sets pricing parameters for the VRGDA.
    /// @param _targetPrice The target price for a token if sold on pace, scaled by 1e18.
    /// @param _priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18.
    /// @param _maxSellable The maximum number of tokens to sell, scaled by 1e18.
    /// @param _timeScale The steepness of the logistic curve, scaled by 1e18.
    constructor(
        int256 _targetPrice,
        int256 _priceDecayPercent,
        int256 _maxSellable,
        int256 _timeScale
    ) VRGDA(_targetPrice, _priceDecayPercent) {
        // Add 1 wad to make the limit inclusive of _maxSellable.
        logisticLimit = _maxSellable + 1e18;

        // Scale by 2e18 to both double it and give it 36 decimals.
        logisticLimitDoubled = logisticLimit * 2e18;

        timeScale = _timeScale;
    }

    /*//////////////////////////////////////////////////////////////
                              PRICING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by.
    /// @param sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for.
    /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is
    /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins.
    function getTargetSaleTime(int256 sold) public view virtual override returns (int256) {
        unchecked {
            return -unsafeWadDiv(wadLn(unsafeDiv(logisticLimitDoubled, sold + logisticLimit) - 1e18), timeScale);
        }
    }
}

File 4 of 27 : VRGDA.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {wadExp, wadLn, wadMul, unsafeWadMul, toWadUnsafe} from "solmate/utils/SignedWadMath.sol";

/// @title Variable Rate Gradual Dutch Auction
/// @author transmissions11 <[email protected]>
/// @author FrankieIsLost <[email protected]>
/// @notice Sell tokens roughly according to an issuance schedule.
abstract contract VRGDA {
    /*//////////////////////////////////////////////////////////////
                            VRGDA PARAMETERS
    //////////////////////////////////////////////////////////////*/

    /// @notice Target price for a token, to be scaled according to sales pace.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 public immutable targetPrice;

    /// @dev Precomputed constant that allows us to rewrite a pow() as an exp().
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal immutable decayConstant;

    /// @notice Sets target price and per time unit price decay for the VRGDA.
    /// @param _targetPrice The target price for a token if sold on pace, scaled by 1e18.
    /// @param _priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18.
    constructor(int256 _targetPrice, int256 _priceDecayPercent) {
        targetPrice = _targetPrice;

        decayConstant = wadLn(1e18 - _priceDecayPercent);

        // The decay constant must be negative for VRGDAs to work.
        require(decayConstant < 0, "NON_NEGATIVE_DECAY_CONSTANT");
    }

    /*//////////////////////////////////////////////////////////////
                              PRICING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Calculate the price of a token according to the VRGDA formula.
    /// @param timeSinceStart Time passed since the VRGDA began, scaled by 1e18.
    /// @param sold The total number of tokens that have been sold so far.
    /// @return The price of a token according to VRGDA, scaled by 1e18.
    function getVRGDAPrice(int256 timeSinceStart, uint256 sold) public view virtual returns (uint256) {
        unchecked {
            // prettier-ignore
            return uint256(wadMul(targetPrice, wadExp(unsafeWadMul(decayConstant,
                // Theoretically calling toWadUnsafe with sold can silently overflow but under
                // any reasonable circumstance it will never be large enough. We use sold + 1 as
                // the VRGDA formula's n param represents the nth token and sold is the n-1th token.
                timeSinceStart - getTargetSaleTime(toWadUnsafe(sold + 1))
            ))));
        }
    }

    /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by.
    /// @param sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for.
    /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is
    /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins.
    function getTargetSaleTime(int256 sold) public view virtual returns (int256);
}

File 5 of 27 : LibGOO.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";

/// @title GOO (Gradual Ownership Optimization) Issuance
/// @author transmissions11 <[email protected]>
/// @author FrankieIsLost <[email protected]>
/// @notice Implementation of the GOO Issuance mechanism.
library LibGOO {
    using FixedPointMathLib for uint256;

    /// @notice Compute goo balance based on emission multiple, last balance, and time elapsed.
    /// @param emissionMultiple The multiple on emissions to consider when computing the balance.
    /// @param lastBalanceWad The last checkpointed balance to apply the emission multiple over time to, scaled by 1e18.
    /// @param timeElapsedWad The time elapsed since the last checkpoint, scaled by 1e18.
    function computeGOOBalance(
        uint256 emissionMultiple,
        uint256 lastBalanceWad,
        uint256 timeElapsedWad
    ) internal pure returns (uint256) {
        unchecked {
            // We use wad math here because timeElapsedWad is, as the name indicates, a wad.
            uint256 timeElapsedSquaredWad = timeElapsedWad.mulWadDown(timeElapsedWad);

            // prettier-ignore
            return lastBalanceWad + // The last recorded balance.

            // Don't need to do wad multiplication since we're
            // multiplying by a plain integer with no decimals.
            // Shift right by 2 is equivalent to division by 4.
            ((emissionMultiple * timeElapsedSquaredWad) >> 2) +

            timeElapsedWad.mulWadDown( // Terms are wads, so must mulWad.
                // No wad multiplication for emissionMultiple * lastBalance
                // because emissionMultiple is a plain integer with no decimals.
                // We multiply the sqrt's radicand by 1e18 because it expects ints.
                (emissionMultiple * lastBalanceWad * 1e18).sqrt()
            );
        }
    }
}

File 6 of 27 : ArtGobblers.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/*                                                                              **,/*,
                                                                     *%@&%#/*,,..........,/(%&@@#*
                                                                 %@%,..............................#@@%
                                                              &&,.....,,...............................,/&@*
                                                            (@*.....**............,/,.......................(@%
                                                           &&......*,............./,.............**............&@
                                                          @#......**.............**..............,*........,*,..,@/
                                                         /@......,/............../,..............,*........../,..*@.
                                                        #@,......................*.............../,..........**...#/
                                                      ,@&,.......................................*..........,/....(@
                                                  *@&(*...................................................../*....(@
                                                 @(..*%@@&%#(#@@@%%%%%&&@@@@@@@@@&&#(///..........................#@
                                                 @%/@@@&%&&&@@&%%%%%%%#(/(((/(/(/(/(/(/(/(/(%%&@@@%(/,............#&
                                                  @@@#/**./@%%%&%#/*************./(%@@@@&(*********(@&&@@@%(.....,&@
                                                 ,@/.//(&@@/.     .#@%/******./&&*,      ./@&********%@/**(@#@@#,..(@
                                                 #%****%@.           %@/****./&@      ,.    %&********%@(**&@...(@#.#@
                                                 &#**./@/  %@&&      .@#****./@*    &@@@@&  .@/******./@@((((@&....(@
                                                 ##**./&@ ,&@@@,     #@/****./@@      @@.  .@&*******./@%****%@@@(,
                                                 ,@/**./%@(.      .*@@/********(&@#*,,,,/&@%/*******./@@&&&@@@#
                                                   @&/**@&/%&&&&&%/**.//////*********./************./@&******@*
                                                     /@@@@&(////#%&@@&(**./#&@@&(//*************./&@(********#@
                                                       .@#**.///*****************(#@@@&&&&&@@@@&%(**********./@,
                                                       @(*****%@#*********************&@#*********************(@
                                                       @****./@#*./@@#//***.///(%@%*****%@*********************#@
                                                      #&****./@%************************&@**********************@%
                                                     .@/******.//*******************./@@(************************@/
                                                     /@**********************************************************(@,
                                                     @#*****************************************************%@@@@@@@.
                                                    *@/*************************************************************#@(
                                                    @%***************************************************************./@(
                     /@@&&&@@                     .@/*******************************************************************&@
                    @%######%@.                   @#***************************./%&&&%(**************#%******************&#
                    @%######&@%&@@.             ,@(***./&#********************#@&#####%@&*************&%****************./@,
               &&*,/@%######&@@@*.*@&,         @@****./@&*******************./%@#######%@#***********./@&*****************(@
              ((...*%@&##%@@,..........,,,,%@&@%/*****&%****************./&@#*%@#######&@*#@%*********./@&*****************(@,
              (@#....(@%#&&,...,/...........@(*******(@(****************(@/...*%@@@@@@%*....&@@@@&@@@@@@%/%@@##(************(@.
              ((./(((%@%#&@/,/&@/...........%&*******%@****************./@%,.................#,............/@%***************#@
              *@@####@@%###%&@(@(...........%&*******%@****************%@,,#%/..............................#@/***************&/
              (#.....,&&####&@..%%..........%%*****(@@#****************#@,...................................@(***************(@
              .@@&%%&@@&####&&.............,@(***%@(**********./#%%%%%##&@&#(,...............................#@****************&.
               &#.....(@%###&@*............%@**%@(*******(&@&%#/////////@%...................................#@***************&@
                 #@@@@&%####&@&&&,........%@./@%*****(@@%////////////////@@@%,...............................#@**************#@
                     @@&&&&@@(    /&@@&%%@&@@@%**./&@(///////////////////@%.................................,@(*********./%@&.
                      (@//@%                @%***&&(//////////////////////(&@(**,,,,./(%&@@@%/*,,****,,***./@@&&&&&&&&#//%@
                      (@//%@               (@(*#@#////////////////////////////%@@%%%&@@#////%@/***************************&&
                      (@//%@  .,,,,/#&&&&&&@&*#@#///////////////////////////////@%//&&///////#@(***************************@&(#@@@@@&(*.
               ,@@@@@&&@//%@,,.,,,,,.,..,,#@./@%////////////////////////////////%@**&&////////(@(**************************&#,,,,,,,,,,,,/(#&@&
          &@%*,,,,,,,,#@//%@,,,,,,,,,,,,,,&%*#@(////////////////////////////////%@**&&/////////&@**************************#@.,,,.,,.,,&#.,,...,%@
       (@/,,,,,,,,,,,,(@(/%@,,,,,,,,,,,,,,&%*#@(////////////////////////////////%@./%@/////////#@(*************************&%,,,(%@@@@#*,.     .,/@.
      &%..    *&@%/,.,#@(*#@*,,.,,,,,,,,,,%@/#@(////////////////////////////////%@**#@/////////#@(*****************.//#%@@@@%%(/,...        ...,,,%&
     ,@*.,.       ../((%&&@@@&%#((///,,,,,/@&(@(////////////////////////////////@&**#@/////////%@%###%&&&&@@@@@@%%#(**,,,,,,.         ..,,,,,,,,,,%#
      @(,,,,,..,            ,..   ..,,,**(%%%&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%(((,,.,.,,,,,,.,..,,,,.,.,,,,.,..,.,,.,,,,,.,,,,,,,,,.*@%
       @%,,,,,,,,,,,,,,,.,.,,,      .,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,.,,,,,,#@@,
        ,@@(,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,.,,.,.,./#%&@@@@@#
         .@#&@@@@@%*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/&@@@@@%&@%((((#@@.
          .@%((((#@@@/#&@@@@&%#/*,.,..,,,,.,,,,,.,.,,,,,,,,,,,,,,,,..,.,..,,...,,,...,,,,,,.,,,,,,,,,,,../#%&@@@@@@@&%((///*********./(((/&&
             %@&%%#/***********./////(((((((####%%&&@@@@@@@@@@@@@@&@@@@@@@@@@@@@@@@&&%%%%%%%%#((((((((%@&#(((((#%@%/*******************./*/

import {Owned} from "solmate/auth/Owned.sol";
import {ERC721} from "solmate/tokens/ERC721.sol";
import {LibString} from "solmate/utils/LibString.sol";
import {MerkleProofLib} from "solmate/utils/MerkleProofLib.sol";
import {FixedPointMathLib} from "solmate/utils/FixedPointMathLib.sol";
import {ERC1155, ERC1155TokenReceiver} from "solmate/tokens/ERC1155.sol";
import {toWadUnsafe, toDaysWadUnsafe} from "solmate/utils/SignedWadMath.sol";

import {LibGOO} from "goo-issuance/LibGOO.sol";
import {LogisticVRGDA} from "VRGDAs/LogisticVRGDA.sol";

import {RandProvider} from "./utils/rand/RandProvider.sol";
import {GobblersERC721} from "./utils/token/GobblersERC721.sol";

import {Goo} from "./Goo.sol";
import {Pages} from "./Pages.sol";

/// @title Art Gobblers NFT
/// @author FrankieIsLost <[email protected]>
/// @author transmissions11 <[email protected]>
/// @notice An experimental decentralized art factory by Justin Roiland and Paradigm.
contract ArtGobblers is GobblersERC721, LogisticVRGDA, Owned, ERC1155TokenReceiver {
    using LibString for uint256;
    using FixedPointMathLib for uint256;

    /*//////////////////////////////////////////////////////////////
                                ADDRESSES
    //////////////////////////////////////////////////////////////*/

    /// @notice The address of the Goo ERC20 token contract.
    Goo public immutable goo;

    /// @notice The address of the Pages ERC721 token contract.
    Pages public immutable pages;

    /// @notice The address which receives gobblers reserved for the team.
    address public immutable team;

    /// @notice The address which receives gobblers reserved for the community.
    address public immutable community;

    /// @notice The address of a randomness provider. This provider will initially be
    /// a wrapper around Chainlink VRF v1, but can be changed in case it is fully sunset.
    RandProvider public randProvider;

    /*//////////////////////////////////////////////////////////////
                            SUPPLY CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Maximum number of mintable gobblers.
    uint256 public constant MAX_SUPPLY = 10000;

    /// @notice Maximum amount of gobblers mintable via mintlist.
    uint256 public constant MINTLIST_SUPPLY = 2000;

    /// @notice Maximum amount of mintable legendary gobblers.
    uint256 public constant LEGENDARY_SUPPLY = 10;

    /// @notice Maximum amount of gobblers split between the reserves.
    /// @dev Set to comprise 20% of the sum of goo mintable gobblers + reserved gobblers.
    uint256 public constant RESERVED_SUPPLY = (MAX_SUPPLY - MINTLIST_SUPPLY - LEGENDARY_SUPPLY) / 5;

    /// @notice Maximum amount of gobblers that can be minted via VRGDA.
    // prettier-ignore
    uint256 public constant MAX_MINTABLE = MAX_SUPPLY
        - MINTLIST_SUPPLY
        - LEGENDARY_SUPPLY
        - RESERVED_SUPPLY;

    /*//////////////////////////////////////////////////////////////
                           METADATA CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @notice Provenance hash for gobbler metadata.
    bytes32 public immutable PROVENANCE_HASH;

    /// @notice URI for gobblers pending reveal.
    string public UNREVEALED_URI;

    /// @notice Base URI for minted gobblers.
    string public BASE_URI;

    /*//////////////////////////////////////////////////////////////
                             MINTLIST STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Merkle root of mint mintlist.
    bytes32 public immutable merkleRoot;

    /// @notice Mapping to keep track of which addresses have claimed from mintlist.
    mapping(address => bool) public hasClaimedMintlistGobbler;

    /*//////////////////////////////////////////////////////////////
                            VRGDA INPUT STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Timestamp for the start of minting.
    uint256 public immutable mintStart;

    /// @notice Number of gobblers minted from goo.
    uint128 public numMintedFromGoo;

    /*//////////////////////////////////////////////////////////////
                         STANDARD GOBBLER STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Id of the most recently minted non legendary gobbler.
    /// @dev Will be 0 if no non legendary gobblers have been minted yet.
    uint128 public currentNonLegendaryId;

    /// @notice The number of gobblers minted to the reserves.
    uint256 public numMintedForReserves;

    /*//////////////////////////////////////////////////////////////
                     LEGENDARY GOBBLER AUCTION STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Initial legendary gobbler auction price.
    uint256 public constant LEGENDARY_GOBBLER_INITIAL_START_PRICE = 69;

    /// @notice The last LEGENDARY_SUPPLY ids are reserved for legendary gobblers.
    uint256 public constant FIRST_LEGENDARY_GOBBLER_ID = MAX_SUPPLY - LEGENDARY_SUPPLY + 1;

    /// @notice Legendary auctions begin each time a multiple of these many gobblers have been minted from goo.
    /// @dev We add 1 to LEGENDARY_SUPPLY because legendary auctions begin only after the first interval.
    uint256 public constant LEGENDARY_AUCTION_INTERVAL = MAX_MINTABLE / (LEGENDARY_SUPPLY + 1);

    /// @notice Struct holding data required for legendary gobbler auctions.
    struct LegendaryGobblerAuctionData {
        // Start price of current legendary gobbler auction.
        uint128 startPrice;
        // Number of legendary gobblers sold so far.
        uint128 numSold;
    }

    /// @notice Data about the current legendary gobbler auction.
    LegendaryGobblerAuctionData public legendaryGobblerAuctionData;

    /*//////////////////////////////////////////////////////////////
                          GOBBLER REVEAL STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Struct holding data required for gobbler reveals.
    struct GobblerRevealsData {
        // Last randomness obtained from the rand provider.
        uint64 randomSeed;
        // Next reveal cannot happen before this timestamp.
        uint64 nextRevealTimestamp;
        // Id of latest gobbler which has been revealed so far.
        uint64 lastRevealedId;
        // Remaining gobblers to be revealed with the current seed.
        uint56 toBeRevealed;
        // Whether we are waiting to receive a seed from the provider.
        bool waitingForSeed;
    }

    /// @notice Data about the current state of gobbler reveals.
    GobblerRevealsData public gobblerRevealsData;

    /*//////////////////////////////////////////////////////////////
                            GOBBLED ART STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Maps gobbler ids to NFT contracts and their ids to the # of those NFT ids gobbled by the gobbler.
    mapping(uint256 => mapping(address => mapping(uint256 => uint256))) public getCopiesOfArtGobbledByGobbler;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event GooBalanceUpdated(address indexed user, uint256 newGooBalance);

    event GobblerClaimed(address indexed user, uint256 indexed gobblerId);
    event GobblerPurchased(address indexed user, uint256 indexed gobblerId, uint256 price);
    event LegendaryGobblerMinted(address indexed user, uint256 indexed gobblerId, uint256[] burnedGobblerIds);
    event ReservedGobblersMinted(address indexed user, uint256 lastMintedGobblerId, uint256 numGobblersEach);

    event RandomnessFulfilled(uint256 randomness);
    event RandomnessRequested(address indexed user, uint256 toBeRevealed);
    event RandProviderUpgraded(address indexed user, RandProvider indexed newRandProvider);

    event GobblersRevealed(address indexed user, uint256 numGobblers, uint256 lastRevealedId);

    event ArtGobbled(address indexed user, uint256 indexed gobblerId, address indexed nft, uint256 id);

    /*//////////////////////////////////////////////////////////////
                                 ERRORS
    //////////////////////////////////////////////////////////////*/

    error InvalidProof();
    error AlreadyClaimed();
    error MintStartPending();

    error SeedPending();
    error RevealsPending();
    error RequestTooEarly();
    error ZeroToBeRevealed();
    error NotRandProvider();

    error ReserveImbalance();

    error Cannibalism();
    error OwnerMismatch(address owner);

    error NoRemainingLegendaryGobblers();
    error CannotBurnLegendary(uint256 gobblerId);
    error InsufficientGobblerAmount(uint256 cost);
    error LegendaryAuctionNotStarted(uint256 gobblersLeft);

    error PriceExceededMax(uint256 currentPrice);

    error NotEnoughRemainingToBeRevealed(uint256 totalRemainingToBeRevealed);

    error UnauthorizedCaller(address caller);

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

    /// @notice Sets VRGDA parameters, mint config, relevant addresses, and URIs.
    /// @param _merkleRoot Merkle root of mint mintlist.
    /// @param _mintStart Timestamp for the start of the VRGDA mint.
    /// @param _goo Address of the Goo contract.
    /// @param _team Address of the team reserve.
    /// @param _community Address of the community reserve.
    /// @param _randProvider Address of the randomness provider.
    /// @param _baseUri Base URI for revealed gobblers.
    /// @param _unrevealedUri URI for unrevealed gobblers.
    /// @param _provenanceHash Provenance Hash for gobbler metadata.
    constructor(
        // Mint config:
        bytes32 _merkleRoot,
        uint256 _mintStart,
        // Addresses:
        Goo _goo,
        Pages _pages,
        address _team,
        address _community,
        RandProvider _randProvider,
        // URIs:
        string memory _baseUri,
        string memory _unrevealedUri,
        // Provenance:
        bytes32 _provenanceHash
    )
        GobblersERC721("Art Gobblers", "GOBBLER")
        Owned(msg.sender)
        LogisticVRGDA(
            69.42e18, // Target price.
            0.31e18, // Price decay percent.
            // Max gobblers mintable via VRGDA.
            toWadUnsafe(MAX_MINTABLE),
            0.0023e18 // Time scale.
        )
    {
        mintStart = _mintStart;
        merkleRoot = _merkleRoot;

        goo = _goo;
        pages = _pages;
        team = _team;
        community = _community;
        randProvider = _randProvider;

        BASE_URI = _baseUri;
        UNREVEALED_URI = _unrevealedUri;

        PROVENANCE_HASH = _provenanceHash;

        // Set the starting price for the first legendary gobbler auction.
        legendaryGobblerAuctionData.startPrice = uint128(LEGENDARY_GOBBLER_INITIAL_START_PRICE);

        // Reveal for initial mint must wait a day from the start of the mint.
        gobblerRevealsData.nextRevealTimestamp = uint64(_mintStart + 1 days);
    }

    /*//////////////////////////////////////////////////////////////
                          MINTLIST CLAIM LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Claim from mintlist, using a merkle proof.
    /// @dev Function does not directly enforce the MINTLIST_SUPPLY limit for gas efficiency. The
    /// limit is enforced during the creation of the merkle proof, which will be shared publicly.
    /// @param proof Merkle proof to verify the sender is mintlisted.
    /// @return gobblerId The id of the gobbler that was claimed.
    function claimGobbler(bytes32[] calldata proof) external returns (uint256 gobblerId) {
        // If minting has not yet begun, revert.
        if (mintStart > block.timestamp) revert MintStartPending();

        // If the user has already claimed, revert.
        if (hasClaimedMintlistGobbler[msg.sender]) revert AlreadyClaimed();

        // If the user's proof is invalid, revert.
        if (!MerkleProofLib.verify(proof, merkleRoot, keccak256(abi.encodePacked(msg.sender)))) revert InvalidProof();

        hasClaimedMintlistGobbler[msg.sender] = true;

        unchecked {
            // Overflow should be impossible due to supply cap of 10,000.
            emit GobblerClaimed(msg.sender, gobblerId = ++currentNonLegendaryId);
        }

        _mint(msg.sender, gobblerId);
    }

    /*//////////////////////////////////////////////////////////////
                              MINTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Mint a gobbler, paying with goo.
    /// @param maxPrice Maximum price to pay to mint the gobbler.
    /// @param useVirtualBalance Whether the cost is paid from the
    /// user's virtual goo balance, or from their ERC20 goo balance.
    /// @return gobblerId The id of the gobbler that was minted.
    function mintFromGoo(uint256 maxPrice, bool useVirtualBalance) external returns (uint256 gobblerId) {
        // No need to check if we're at MAX_MINTABLE,
        // gobblerPrice() will revert once we reach it due to its
        // logistic nature. It will also revert prior to the mint start.
        uint256 currentPrice = gobblerPrice();

        // If the current price is above the user's specified max, revert.
        if (currentPrice > maxPrice) revert PriceExceededMax(currentPrice);

        // Decrement the user's goo balance by the current
        // price, either from virtual balance or ERC20 balance.
        useVirtualBalance
            ? updateUserGooBalance(msg.sender, currentPrice, GooBalanceUpdateType.DECREASE)
            : goo.burnForGobblers(msg.sender, currentPrice);

        unchecked {
            ++numMintedFromGoo; // Overflow should be impossible due to the supply cap.

            emit GobblerPurchased(msg.sender, gobblerId = ++currentNonLegendaryId, currentPrice);
        }

        _mint(msg.sender, gobblerId);
    }

    /// @notice Gobbler pricing in terms of goo.
    /// @dev Will revert if called before minting starts
    /// or after all gobblers have been minted via VRGDA.
    /// @return Current price of a gobbler in terms of goo.
    function gobblerPrice() public view returns (uint256) {
        // We need checked math here to cause underflow
        // before minting has begun, preventing mints.
        uint256 timeSinceStart = block.timestamp - mintStart;

        return getVRGDAPrice(toDaysWadUnsafe(timeSinceStart), numMintedFromGoo);
    }

    /*//////////////////////////////////////////////////////////////
                     LEGENDARY GOBBLER AUCTION LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Mint a legendary gobbler by burning multiple standard gobblers.
    /// @param gobblerIds The ids of the standard gobblers to burn.
    /// @return gobblerId The id of the legendary gobbler that was minted.
    function mintLegendaryGobbler(uint256[] calldata gobblerIds) external returns (uint256 gobblerId) {
        // Get the number of legendary gobblers sold up until this point.
        uint256 numSold = legendaryGobblerAuctionData.numSold;

        gobblerId = FIRST_LEGENDARY_GOBBLER_ID + numSold; // Assign id.

        // This will revert if the auction hasn't started yet or legendaries
        // have sold out entirely, so there is no need to check here as well.
        uint256 cost = legendaryGobblerPrice();

        if (gobblerIds.length < cost) revert InsufficientGobblerAmount(cost);

        // Overflow should not occur in here, as most math is on emission multiples, which are inherently small.
        unchecked {
            uint256 burnedMultipleTotal; // The legendary's emissionMultiple will be 2x the sum of the gobblers burned.

            /*//////////////////////////////////////////////////////////////
                                    BATCH BURN LOGIC
            //////////////////////////////////////////////////////////////*/

            uint256 id; // Storing outside the loop saves ~7 gas per iteration.

            for (uint256 i = 0; i < cost; ++i) {
                id = gobblerIds[i];

                if (id >= FIRST_LEGENDARY_GOBBLER_ID) revert CannotBurnLegendary(id);

                GobblerData storage gobbler = getGobblerData[id];

                require(gobbler.owner == msg.sender, "WRONG_FROM");

                burnedMultipleTotal += gobbler.emissionMultiple;

                delete getApproved[id];

                emit Transfer(msg.sender, gobbler.owner = address(0), id);
            }

            /*//////////////////////////////////////////////////////////////
                                 LEGENDARY MINTING LOGIC
            //////////////////////////////////////////////////////////////*/

            // The legendary's emissionMultiple is 2x the sum of the multiples of the gobblers burned.
            getGobblerData[gobblerId].emissionMultiple = uint32(burnedMultipleTotal * 2);

            // Update the user's user data struct in one big batch. We add burnedMultipleTotal to their
            // emission multiple (not burnedMultipleTotal * 2) to account for the standard gobblers that
            // were burned and hence should have their multiples subtracted from the user's total multiple.
            getUserData[msg.sender].lastBalance = uint128(gooBalance(msg.sender)); // Checkpoint balance.
            getUserData[msg.sender].lastTimestamp = uint64(block.timestamp); // Store time alongside it.
            getUserData[msg.sender].emissionMultiple += uint32(burnedMultipleTotal); // Update multiple.
            // Update the total number of gobblers owned by the user. The call to _mint
            // below will increase the count by 1 to account for the new legendary gobbler.
            getUserData[msg.sender].gobblersOwned -= uint32(cost);

            // New start price is the max of LEGENDARY_GOBBLER_INITIAL_START_PRICE and cost * 2.
            legendaryGobblerAuctionData.startPrice = uint128(
                cost <= LEGENDARY_GOBBLER_INITIAL_START_PRICE / 2 ? LEGENDARY_GOBBLER_INITIAL_START_PRICE : cost * 2
            );
            legendaryGobblerAuctionData.numSold = uint128(numSold + 1); // Increment the # of legendaries sold.

            // If gobblerIds has 1,000 elements this should cost around ~270,000 gas.
            emit LegendaryGobblerMinted(msg.sender, gobblerId, gobblerIds[:cost]);

            _mint(msg.sender, gobblerId);
        }
    }

    /// @notice Calculate the legendary gobbler price in terms of gobblers, according to a linear decay function.
    /// @dev The price of a legendary gobbler decays as gobblers are minted. The first legendary auction begins when
    /// 1 LEGENDARY_AUCTION_INTERVAL worth of gobblers are minted, and the price decays linearly while the next interval of
    /// gobblers are minted. Every time an additional interval is minted, a new auction begins until all legendaries have been sold.
    /// @dev Will revert if the auction hasn't started yet or legendaries have sold out entirely.
    /// @return The current price of the legendary gobbler being auctioned, in terms of gobblers.
    function legendaryGobblerPrice() public view returns (uint256) {
        // Retrieve and cache various auction parameters and variables.
        uint256 startPrice = legendaryGobblerAuctionData.startPrice;
        uint256 numSold = legendaryGobblerAuctionData.numSold;

        // If all legendary gobblers have been sold, there are none left to auction.
        if (numSold == LEGENDARY_SUPPLY) revert NoRemainingLegendaryGobblers();

        unchecked {
            // Get and cache the number of standard gobblers sold via VRGDA up until this point.
            uint256 mintedFromGoo = numMintedFromGoo;

            // The number of gobblers minted at the start of the auction is computed by multiplying the # of
            // intervals that must pass before the next auction begins by the number of gobblers in each interval.
            uint256 numMintedAtStart = (numSold + 1) * LEGENDARY_AUCTION_INTERVAL;

            // If not enough gobblers have been minted to start the auction yet, return how many need to be minted.
            if (numMintedAtStart > mintedFromGoo) revert LegendaryAuctionNotStarted(numMintedAtStart - mintedFromGoo);

            // Compute how many gobblers were minted since the auction began.
            uint256 numMintedSinceStart = mintedFromGoo - numMintedAtStart;

            // prettier-ignore
            // If we've minted the full interval or beyond it, the price has decayed to 0.
            if (numMintedSinceStart >= LEGENDARY_AUCTION_INTERVAL) return 0;
            // Otherwise decay the price linearly based on what fraction of the interval has been minted.
            else return FixedPointMathLib.unsafeDivUp(startPrice * (LEGENDARY_AUCTION_INTERVAL - numMintedSinceStart), LEGENDARY_AUCTION_INTERVAL);
        }
    }

    /*//////////////////////////////////////////////////////////////
                            RANDOMNESS LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Request a new random seed for revealing gobblers.
    function requestRandomSeed() external returns (bytes32) {
        uint256 nextRevealTimestamp = gobblerRevealsData.nextRevealTimestamp;

        // A new random seed cannot be requested before the next reveal timestamp.
        if (block.timestamp < nextRevealTimestamp) revert RequestTooEarly();

        // A random seed can only be requested when all gobblers from the previous seed have been revealed.
        // This prevents a user from requesting additional randomness in hopes of a more favorable outcome.
        if (gobblerRevealsData.toBeRevealed != 0) revert RevealsPending();

        unchecked {
            // Prevent revealing while we wait for the seed.
            gobblerRevealsData.waitingForSeed = true;

            // Compute the number of gobblers to be revealed with the seed.
            uint256 toBeRevealed = currentNonLegendaryId - gobblerRevealsData.lastRevealedId;

            // Ensure that there are more than 0 gobblers to be revealed,
            // otherwise the contract could waste LINK revealing nothing.
            if (toBeRevealed == 0) revert ZeroToBeRevealed();

            // Lock in the number of gobblers to be revealed from seed.
            gobblerRevealsData.toBeRevealed = uint56(toBeRevealed);

            // We enable reveals for a set of gobblers every 24 hours.
            // Timestamp overflow is impossible on human timescales.
            gobblerRevealsData.nextRevealTimestamp = uint64(nextRevealTimestamp + 1 days);

            emit RandomnessRequested(msg.sender, toBeRevealed);
        }

        // Call out to the randomness provider.
        return randProvider.requestRandomBytes();
    }

    /// @notice Callback from rand provider. Sets randomSeed. Can only be called by the rand provider.
    /// @param randomness The 256 bits of verifiable randomness provided by the rand provider.
    function acceptRandomSeed(bytes32, uint256 randomness) external {
        // The caller must be the randomness provider, revert in the case it's not.
        if (msg.sender != address(randProvider)) revert NotRandProvider();

        // The unchecked cast to uint64 is equivalent to moduloing the randomness by 2**64.
        gobblerRevealsData.randomSeed = uint64(randomness); // 64 bits of randomness is plenty.

        gobblerRevealsData.waitingForSeed = false; // We have the seed now, open up reveals.

        emit RandomnessFulfilled(randomness);
    }

    /// @notice Upgrade the rand provider contract. Useful if current VRF is sunset.
    /// @param newRandProvider The new randomness provider contract address.
    function upgradeRandProvider(RandProvider newRandProvider) external onlyOwner {
        // Reset reveal state when we upgrade while the seed is pending. This gives us a
        // safeguard against malfunctions since we won't be stuck waiting for a seed forever.
        if (gobblerRevealsData.waitingForSeed) {
            gobblerRevealsData.waitingForSeed = false;
            gobblerRevealsData.toBeRevealed = 0;
            gobblerRevealsData.nextRevealTimestamp -= 1 days;
        }

        randProvider = newRandProvider; // Update the randomness provider.

        emit RandProviderUpgraded(msg.sender, newRandProvider);
    }

    /*//////////////////////////////////////////////////////////////
                          GOBBLER REVEAL LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Knuth shuffle to progressively reveal
    /// new gobblers using entropy from a random seed.
    /// @param numGobblers The number of gobblers to reveal.
    function revealGobblers(uint256 numGobblers) external {
        uint256 randomSeed = gobblerRevealsData.randomSeed;

        uint256 lastRevealedId = gobblerRevealsData.lastRevealedId;

        uint256 totalRemainingToBeRevealed = gobblerRevealsData.toBeRevealed;

        // Can't reveal if we're still waiting for a new seed.
        if (gobblerRevealsData.waitingForSeed) revert SeedPending();

        // Can't reveal more gobblers than are currently remaining to be revealed with the seed.
        if (numGobblers > totalRemainingToBeRevealed) revert NotEnoughRemainingToBeRevealed(totalRemainingToBeRevealed);

        // Implements a Knuth shuffle. If something in
        // here can overflow, we've got bigger problems.
        unchecked {
            for (uint256 i = 0; i < numGobblers; ++i) {
                /*//////////////////////////////////////////////////////////////
                                      DETERMINE RANDOM SWAP
                //////////////////////////////////////////////////////////////*/

                // Number of ids that have not been revealed. Subtract 1
                // because we don't want to include any legendaries in the swap.
                uint256 remainingIds = FIRST_LEGENDARY_GOBBLER_ID - lastRevealedId - 1;

                // Randomly pick distance for swap.
                uint256 distance = randomSeed % remainingIds;

                // Current id is consecutive to last reveal.
                uint256 currentId = ++lastRevealedId;

                // Select swap id, adding distance to next reveal id.
                uint256 swapId = currentId + distance;

                /*//////////////////////////////////////////////////////////////
                                       GET INDICES FOR IDS
                //////////////////////////////////////////////////////////////*/

                // Get the index of the swap id.
                uint64 swapIndex = getGobblerData[swapId].idx == 0
                    ? uint64(swapId) // Hasn't been shuffled before.
                    : getGobblerData[swapId].idx; // Shuffled before.

                // Get the owner of the current id.
                address currentIdOwner = getGobblerData[currentId].owner;

                // Get the index of the current id.
                uint64 currentIndex = getGobblerData[currentId].idx == 0
                    ? uint64(currentId) // Hasn't been shuffled before.
                    : getGobblerData[currentId].idx; // Shuffled before.

                /*//////////////////////////////////////////////////////////////
                                  SWAP INDICES AND SET MULTIPLE
                //////////////////////////////////////////////////////////////*/

                // Determine the current id's new emission multiple.
                uint256 newCurrentIdMultiple = 9; // For beyond 7963.

                // The branchless expression below is equivalent to:
                //      if (swapIndex <= 3054) newCurrentIdMultiple = 6;
                // else if (swapIndex <= 5672) newCurrentIdMultiple = 7;
                // else if (swapIndex <= 7963) newCurrentIdMultiple = 8;
                assembly {
                    // prettier-ignore
                    newCurrentIdMultiple := sub(sub(sub(
                        newCurrentIdMultiple,
                        lt(swapIndex, 7964)),
                        lt(swapIndex, 5673)),
                        lt(swapIndex, 3055)
                    )
                }

                // Swap the index and multiple of the current id.
                getGobblerData[currentId].idx = swapIndex;
                getGobblerData[currentId].emissionMultiple = uint32(newCurrentIdMultiple);

                // Swap the index of the swap id.
                getGobblerData[swapId].idx = currentIndex;

                /*//////////////////////////////////////////////////////////////
                                   UPDATE CURRENT ID MULTIPLE
                //////////////////////////////////////////////////////////////*/

                // Update the user data for the owner of the current id.
                getUserData[currentIdOwner].lastBalance = uint128(gooBalance(currentIdOwner));
                getUserData[currentIdOwner].lastTimestamp = uint64(block.timestamp);
                getUserData[currentIdOwner].emissionMultiple += uint32(newCurrentIdMultiple);

                // Update the random seed to choose a new distance for the next iteration.
                // It is critical that we cast to uint64 here, as otherwise the random seed
                // set after calling revealGobblers(1) thrice would differ from the seed set
                // after calling revealGobblers(3) a single time. This would enable an attacker
                // to choose from a number of different seeds and use whichever is most favorable.
                // Equivalent to randomSeed = uint64(uint256(keccak256(abi.encodePacked(randomSeed))))
                assembly {
                    mstore(0, randomSeed) // Store the random seed in scratch space.

                    // Moduloing by 2 ** 64 is equivalent to a uint64 cast.
                    randomSeed := mod(keccak256(0, 32), exp(2, 64))
                }
            }

            // Update all relevant reveal state.
            gobblerRevealsData.randomSeed = uint64(randomSeed);
            gobblerRevealsData.lastRevealedId = uint64(lastRevealedId);
            gobblerRevealsData.toBeRevealed = uint56(totalRemainingToBeRevealed - numGobblers);

            emit GobblersRevealed(msg.sender, numGobblers, lastRevealedId);
        }
    }

    /*//////////////////////////////////////////////////////////////
                                URI LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns a token's URI if it has been minted.
    /// @param gobblerId The id of the token to get the URI for.
    function tokenURI(uint256 gobblerId) public view virtual override returns (string memory) {
        // Between 0 and lastRevealed are revealed normal gobblers.
        if (gobblerId <= gobblerRevealsData.lastRevealedId) {
            if (gobblerId == 0) revert("NOT_MINTED"); // 0 is not a valid id for Art Gobblers.

            return string.concat(BASE_URI, uint256(getGobblerData[gobblerId].idx).toString());
        }

        // Between lastRevealed + 1 and currentNonLegendaryId are minted but not revealed.
        if (gobblerId <= currentNonLegendaryId) return UNREVEALED_URI;

        // Between currentNonLegendaryId and FIRST_LEGENDARY_GOBBLER_ID are unminted.
        if (gobblerId < FIRST_LEGENDARY_GOBBLER_ID) revert("NOT_MINTED");

        // Between FIRST_LEGENDARY_GOBBLER_ID and FIRST_LEGENDARY_GOBBLER_ID + numSold are minted legendaries.
        if (gobblerId < FIRST_LEGENDARY_GOBBLER_ID + legendaryGobblerAuctionData.numSold)
            return string.concat(BASE_URI, gobblerId.toString());

        revert("NOT_MINTED"); // Unminted legendaries and invalid token ids.
    }

    /*//////////////////////////////////////////////////////////////
                            GOBBLE ART LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Feed a gobbler a work of art.
    /// @param gobblerId The gobbler to feed the work of art.
    /// @param nft The ERC721 or ERC1155 contract of the work of art.
    /// @param id The id of the work of art.
    /// @param isERC1155 Whether the work of art is an ERC1155 token.
    function gobble(
        uint256 gobblerId,
        address nft,
        uint256 id,
        bool isERC1155
    ) external {
        // Get the owner of the gobbler to feed.
        address owner = getGobblerData[gobblerId].owner;

        // The caller must own the gobbler they're feeding.
        if (owner != msg.sender) revert OwnerMismatch(owner);

        // Gobblers have taken a vow not to eat other gobblers.
        if (nft == address(this)) revert Cannibalism();

        unchecked {
            // Increment the # of copies gobbled by the gobbler. Unchecked is
            // safe, as an NFT can't have more than type(uint256).max copies.
            ++getCopiesOfArtGobbledByGobbler[gobblerId][nft][id];
        }

        emit ArtGobbled(msg.sender, gobblerId, nft, id);

        isERC1155
            ? ERC1155(nft).safeTransferFrom(msg.sender, address(this), id, 1, "")
            : ERC721(nft).transferFrom(msg.sender, address(this), id);
    }

    /*//////////////////////////////////////////////////////////////
                                GOO LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Calculate a user's virtual goo balance.
    /// @param user The user to query balance for.
    function gooBalance(address user) public view returns (uint256) {
        // Compute the user's virtual goo balance using LibGOO.
        // prettier-ignore
        return LibGOO.computeGOOBalance(
            getUserData[user].emissionMultiple,
            getUserData[user].lastBalance,
            uint256(toDaysWadUnsafe(block.timestamp - getUserData[user].lastTimestamp))
        );
    }

    /// @notice Add goo to your emission balance,
    /// burning the corresponding ERC20 balance.
    /// @param gooAmount The amount of goo to add.
    function addGoo(uint256 gooAmount) external {
        // Burn goo being added to gobbler.
        goo.burnForGobblers(msg.sender, gooAmount);

        // Increase msg.sender's virtual goo balance.
        updateUserGooBalance(msg.sender, gooAmount, GooBalanceUpdateType.INCREASE);
    }

    /// @notice Remove goo from your emission balance, and
    /// add the corresponding amount to your ERC20 balance.
    /// @param gooAmount The amount of goo to remove.
    function removeGoo(uint256 gooAmount) external {
        // Decrease msg.sender's virtual goo balance.
        updateUserGooBalance(msg.sender, gooAmount, GooBalanceUpdateType.DECREASE);

        // Mint the corresponding amount of ERC20 goo.
        goo.mintForGobblers(msg.sender, gooAmount);
    }

    /// @notice Burn an amount of a user's virtual goo balance. Only callable
    /// by the Pages contract to enable purchasing pages with virtual balance.
    /// @param user The user whose virtual goo balance we should burn from.
    /// @param gooAmount The amount of goo to burn from the user's virtual balance.
    function burnGooForPages(address user, uint256 gooAmount) external {
        // The caller must be the Pages contract, revert otherwise.
        if (msg.sender != address(pages)) revert UnauthorizedCaller(msg.sender);

        // Burn the requested amount of goo from the user's virtual goo balance.
        // Will revert if the user doesn't have enough goo in their virtual balance.
        updateUserGooBalance(user, gooAmount, GooBalanceUpdateType.DECREASE);
    }

    /// @dev An enum for representing whether to
    /// increase or decrease a user's goo balance.
    enum GooBalanceUpdateType {
        INCREASE,
        DECREASE
    }

    /// @notice Update a user's virtual goo balance.
    /// @param user The user whose virtual goo balance we should update.
    /// @param gooAmount The amount of goo to update the user's virtual balance by.
    /// @param updateType Whether to increase or decrease the user's balance by gooAmount.
    function updateUserGooBalance(
        address user,
        uint256 gooAmount,
        GooBalanceUpdateType updateType
    ) internal {
        // Will revert due to underflow if we're decreasing by more than the user's current balance.
        // Don't need to do checked addition in the increase case, but we do it anyway for convenience.
        uint256 updatedBalance = updateType == GooBalanceUpdateType.INCREASE
            ? gooBalance(user) + gooAmount
            : gooBalance(user) - gooAmount;

        // Snapshot the user's new goo balance with the current timestamp.
        getUserData[user].lastBalance = uint128(updatedBalance);
        getUserData[user].lastTimestamp = uint64(block.timestamp);

        emit GooBalanceUpdated(user, updatedBalance);
    }

    /*//////////////////////////////////////////////////////////////
                     RESERVED GOBBLERS MINTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Mint a number of gobblers to the reserves.
    /// @param numGobblersEach The number of gobblers to mint to each reserve.
    /// @dev Gobblers minted to reserves cannot comprise more than 20% of the sum of
    /// the supply of goo minted gobblers and the supply of gobblers minted to reserves.
    function mintReservedGobblers(uint256 numGobblersEach) external returns (uint256 lastMintedGobblerId) {
        unchecked {
            // Optimistically increment numMintedForReserves, may be reverted below.
            // Overflow in this calculation is possible but numGobblersEach would have to
            // be so large that it would cause the loop in _batchMint to run out of gas quickly.
            uint256 newNumMintedForReserves = numMintedForReserves += (numGobblersEach * 2);

            // Ensure that after this mint gobblers minted to reserves won't comprise more than 20% of
            // the sum of the supply of goo minted gobblers and the supply of gobblers minted to reserves.
            if (newNumMintedForReserves > (numMintedFromGoo + newNumMintedForReserves) / 5) revert ReserveImbalance();
        }

        // Mint numGobblersEach gobblers to both the team and community reserve.
        lastMintedGobblerId = _batchMint(team, numGobblersEach, currentNonLegendaryId);
        lastMintedGobblerId = _batchMint(community, numGobblersEach, lastMintedGobblerId);

        currentNonLegendaryId = uint128(lastMintedGobblerId); // Set currentNonLegendaryId.

        emit ReservedGobblersMinted(msg.sender, lastMintedGobblerId, numGobblersEach);
    }

    /*//////////////////////////////////////////////////////////////
                          CONVENIENCE FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @notice Convenience function to get emissionMultiple for a gobbler.
    /// @param gobblerId The gobbler to get emissionMultiple for.
    function getGobblerEmissionMultiple(uint256 gobblerId) external view returns (uint256) {
        return getGobblerData[gobblerId].emissionMultiple;
    }

    /// @notice Convenience function to get emissionMultiple for a user.
    /// @param user The user to get emissionMultiple for.
    function getUserEmissionMultiple(address user) external view returns (uint256) {
        return getUserData[user].emissionMultiple;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public override {
        require(from == getGobblerData[id].owner, "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        delete getApproved[id];

        getGobblerData[id].owner = to;

        unchecked {
            uint32 emissionMultiple = getGobblerData[id].emissionMultiple; // Caching saves gas.

            // We update their last balance before updating their emission multiple to avoid
            // penalizing them by retroactively applying their new (lower) emission multiple.
            getUserData[from].lastBalance = uint128(gooBalance(from));
            getUserData[from].lastTimestamp = uint64(block.timestamp);
            getUserData[from].emissionMultiple -= emissionMultiple;
            getUserData[from].gobblersOwned -= 1;

            // We update their last balance before updating their emission multiple to avoid
            // overpaying them by retroactively applying their new (higher) emission multiple.
            getUserData[to].lastBalance = uint128(gooBalance(to));
            getUserData[to].lastTimestamp = uint64(block.timestamp);
            getUserData[to].emissionMultiple += emissionMultiple;
            getUserData[to].gobblersOwned += 1;
        }

        emit Transfer(from, to, id);
    }
}

File 7 of 27 : Goo.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {ERC20} from "solmate/tokens/ERC20.sol";

/*                                                                %#/*********(&,
                                                              .#*********************#.
                                                            #****./*********************%
                                                          %*******************************%
                                                        &**********************************,((
                                                       @(*,***********************************#&
                                                    (*********************#***********************(
                                                  ,%@/**************#%***%**&***%*******************,
                                                  /********************#****#*#******,**************%
                                                 ,************,#(*****************(#/&(*,*,*********#
                                                 **************(%%(&************#@%(///************(
                                                ./**************,*./##****************************#*%
                                               #**&**************************************************&@@@@@&@&%#((./.
                                              (*******************@&%&@@@.   /    %  &********(@/,****,,,*,,,****,,*,**********,*,
                                             &******************#  /    *     /   /    .. %/****(******************,**&***********./
                                  /%(*******************&***./#    #.#%%    .,    .,   ##&&@****#***********************************.
                         *#(*,**************************(***(///.*     *     #     #   .  %*****(/*************************************&
                 *(***********************************.//****&    #     #    (#&((%@*,*&(******(%************./@#*   *%&%(/&*************(
               #,**************************************,&******&..*#&(*****,,,,/********************************             (/******,**,**,
              %*****************************************.//**************#**************************************               .(***********#
             (*************************./************************************************************************              @**************
             ,**********&@@@&&%#        &,**********************************************************************@             ./*,%*,********./
            ***********                .************@(*************(&#///////////////.//#&%/*****************&*,,                &************%
           (**********.                 .%********************(&./////////////////////////////(%******************                *(**&,&##*
          #**********(,                &,*./***************%(///////////////////////////////////*&****************
        (************%                %,*****************&///////////////////////////////////////*(***************.
      .(***************(             #******************&//////////////////////////////////////////****************
     .&*************%*./            .*******************%/////////////////////////////////////////****************##
      .*************%*%             (********************#(///////////////////////////////////(#*****************&**,***,.
           #***./,***%              #**********************,%%*./////////////////////////*(@*******************(/****./********,((
           @@,                    &**@*****************************./(%@&%%((((((%&&%(*********************************&,**********.
                         .   .#,,*****./&/*****************************************************************************************
                          %,******************************************************************************************************#
                       %*******@*****************************************************./#%%,...((,           .,********************(
                     ,*******************************@&(**./%&%*        .,//(//////////,                           ,************./
                      /**************************&*                      ////*(/////////                            ***(*********%
                       (*********************(#                         ..///////////(//(                          .***********./
                         #******************%                       *..,,,(//////////(//(*.//,                     %***************&
                           %*****************                   ////////&&&&&&&&%#(//(&@&#(#@@                    &*********************#
                             #****************.                 ,//(//////(@@%%%%%///////****&                   &************************(
                           .**&***(************./               .@.,(///(/(.//(***((*(//*****@/&                ,*************************./
                            &********************#             .(#(@#//(****(//(*****(/(&(..&(                  ./*********************(#.
                        #/***********************./          /,,./*((#%@(%&%(((((((#%&&&/(#(#@(
                      #*,***********************,*&                 .%@@@&#,  ///(/*
                     (*************************%                             ..(/,./(,.,*
                      /#/*./(%&(.*/

/// @title Goo Token (GOO)
/// @author FrankieIsLost <[email protected]>
/// @author transmissions11 <[email protected]>
/// @notice Goo is the in-game token for ArtGobblers. It's a standard ERC20
/// token that can be burned and minted by the gobblers and pages contract.
contract Goo is ERC20("Goo", "GOO", 18) {
    /*//////////////////////////////////////////////////////////////
                                ADDRESSES
    //////////////////////////////////////////////////////////////*/

    /// @notice The address of the Art Gobblers contract.
    address public immutable artGobblers;

    /// @notice The address of the Pages contract.
    address public immutable pages;

    /*//////////////////////////////////////////////////////////////
                                 ERRORS
    //////////////////////////////////////////////////////////////*/

    error Unauthorized();

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

    /// @notice Sets the addresses of relevant contracts.
    /// @param _artGobblers Address of the ArtGobblers contract.
    /// @param _pages Address of the Pages contract.
    constructor(address _artGobblers, address _pages) {
        artGobblers = _artGobblers;
        pages = _pages;
    }

    /*//////////////////////////////////////////////////////////////
                             MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Requires caller address to match user address.
    modifier only(address user) {
        if (msg.sender != user) revert Unauthorized();

        _;
    }

    /// @notice Mint any amount of goo to a user. Can only be called by ArtGobblers.
    /// @param to The address of the user to mint goo to.
    /// @param amount The amount of goo to mint.
    function mintForGobblers(address to, uint256 amount) external only(artGobblers) {
        _mint(to, amount);
    }

    /// @notice Burn any amount of goo from a user. Can only be called by ArtGobblers.
    /// @param from The address of the user to burn goo from.
    /// @param amount The amount of goo to burn.
    function burnForGobblers(address from, uint256 amount) external only(artGobblers) {
        _burn(from, amount);
    }

    /// @notice Burn any amount of goo from a user. Can only be called by Pages.
    /// @param from The address of the user to burn goo from.
    /// @param amount The amount of goo to burn.
    function burnForPages(address from, uint256 amount) external only(pages) {
        _burn(from, amount);
    }
}

File 8 of 27 : Pages.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {LibString} from "solmate/utils/LibString.sol";
import {toDaysWadUnsafe} from "solmate/utils/SignedWadMath.sol";

import {LogisticToLinearVRGDA} from "VRGDAs/LogisticToLinearVRGDA.sol";

import {PagesERC721} from "./utils/token/PagesERC721.sol";

import {Goo} from "./Goo.sol";
import {ArtGobblers} from "./ArtGobblers.sol";

/*                                                                                   &@./(
                                                                                    &//*&
                                                                                   @/*.&
                                                                                 (#/./%
                                                             .,(#%%&@@&%#(/,    *#./#,                   .*(%@@@@&%#((//////(#&@%.
                                                      #&@&/*./*************.///&@%&@@@@@@@@&%#(///**********./********************#&
                                                  &@(/*****************************************************************************#/
                                               (&**********************************************************************************@
                                              @**********************************************************************************(&
                                             &/*******************************************************************************%@.
           ,(                                *&/***********************************************************************./#@@%(((#@%
            .//                                (@/***./*****************************************************.///#&@@@&##(((#(##((##%&
             /*.//                                 *@@&#(/////(#&@@@@@@@@@@@@@@@@@@@@@@@@@@@&&&&%%%%%####(#(((((((##(((##((#(###((((#@&
              (****./                               @###(###(((((###(##((#(((((#(#####((((#(((((((#((((((((((((((((((((#(##(####((((#(#@
              /******./                            &#((#########(############((#(######(########(####(##(###%&&&%########%&&%###((((####@
              ,********./                         /&((((#(#((##(((#(((#((########(###((((((((((((((((#((%&#((#((#((((((((((#((((((((((((#@
              .**********(                        @#(#(#####(###(#&&&#(((((((((#(((%@%##########(######&#((#&#(((##((((%&@%#((##(#(((#((#%&
               /**********(                      /%(#(((((#((#&%(((#(###(#########(((###(((#####(###((#(%@%###(###((((##(((##&%#(##(((##(#@,
               (**********./                     @#(#(((((###((#(#((#(((((###(((#(#%@@###((#####(###((#&%&&/.              .*#&&@#((((((((%@
               ./**********(                    ,%((((((((##((((#&@#(##((((((##%%&&&&%%#&##((###(##((%*                          .@#(##((##@.
                ./********./                    %#((#(((((#####@##(##%@&(.                %#((#((###%*            *(,.             %#((#(((@,
                  /********(                    @#((#((((####@#%@#                         .&((((##(&           &@@@@@@%            &#((#&#&,
                    /******(                   ,&(#(#((((###@&                              .&##(###&            /@@@@@@            (%((((#@%
                      ./*#@@@@*                (%#(((((#((#&           #@@@@@&.              (%#(#((&.            /#.               ##(#(#(##@&
                       @%(/((((&               &####(((((#@            .@@@@@@@               &((##(#@@(.                      .#&%#&(#####((##@.
                      /#(((((((##         (#*  @##(#(((((#@             ,@@@/*               @&((####(&&#(##%%&&&%%##%&&&&&%##(((#%%(###((####((&&
                       #((((%@@@@@.    @#(((#@*&##(((((((#@                              .%&#@#((####(((#((#(((((((((((((((((((##%((#(###((#####(#@
                       #@/,,,@#((#%(  @((&%,,,&&%((((((#(#&,                        .%@%#((#@%(((###((((#(((##%&&&&&&&&&&&%#((((####(###(###(###((#@%
                   #@##&(,,,(&(##(#@ &###@**#@@@@(((((((##(@@@#,            ,(@@&##(((((((((#((((#####((#%@@@@@@@@@@@@@@@@@&&%%################%%(##@&
                %&##((((#&@@%(((((%# &##(####(#&@&(((((((#(##@#((((((((##((###(#(#(((#%&##(#####(####(##@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@&%##(((#((%@
             *&#((#######((((###(%&@ (#(####(((#&@%(((((####(##%&##(#((((###(###%@@%###(##%%%###(#(####@@@@&%###((##((((((((##((((((((##((((((#((##(#@
           (&((((((((((((((((##@&/((@@&####(((((((#&&%#(#((((((((#(((((#((###(#((#&@&&@@@@@@@@%((((((#((((#((((((((((((((((((((((((((((((((((((((((((#
         .@#(##((((((((((#((&@#%@@&%%&@&##(((###(#####(#@%##(###((#######(((#(#%@#(((#((#(######(###############(###############(###############(##(##
        (%(#((#((((((((((#&@%#(%%,,,,&#(#@######(####(((((%%(#(#(###(#(((#%@@@@@&##(####(((#(#(#(#######(#######(#######(#######(#######(#######(##((#
       (%((####((###(#&@########@*,,,@#(#%@(((#((#######(((#&(##(###(%@@@@@%##((#@&%####&@@%(###(###############(###############(###############(###(#
       @((##(##(((#@%#(#((##(#(#(#&@&#(##@&&@@@&##(#(###(#((&##%&@@%##(#(#(((####((((&%((###((#&####(###(###(###(###(###(###(###(###(###(###(###(####(
       @((#(((##@%(((((((######((###(#&@%#(#(#(#(##########(%%((((#((((#(((((((((((((#&&#(###&&(((##############(###############(###############(#####
       .%(#(#%@#((###(#((########&@@((((&#(#####((######(##(%&#((#######(#######(#####(#####((##(#######(#######(#######(#######(#######(#######(#####
         &##@#(#((##((#####(#&&#(((#&(((@#######(##########(&%%#(#((##(#########(############((((###############(###############(###############(#####
          *%(((#(#(#(#(#(#%@#(((((((((##(((((((#(#(#(#(#(#(#@###(((#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#
         ,&(##(#(#####((%@#(###((((((((####((###(########((%@&##(###############(###############(###############(###############(###############(#####
         &#((((#(##(####&%(##(((((%@%((#(#######(######(##%@&#((((######(#######(#######(#######(#######(#######(#######(#######(#######(#######(#####
         %###((#(##########((###((##((##########(####(#((&&###(((###############(###############(###############(###############(###############(#####
          &#(#(#(###(###(##((###(###((##(###(###(###((#&&#######(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(###(#
           %%(#(###(#(#(########(#####(#########(###((#&%#(#####(###############(###############(###############(###############(#############(((#####
              (&%%#%#%#((#(#####(#######(#######(######((#&%(###(#######(#######(#######(#######(#######(#######(#######(#######((##(##((#(#(###(((###
                       ##(#(####(###############(######(((((####(###############(###############(###############(#############(##(#((##%%&@@@@&&&%%%%%
                         &((((((((((((((((((((((((((((((((((((##((((((((((((((((((((((((((((((((((((((((((((((((((((((((##(((##&@@&&%%%%%%%%%%%%%%%%%%
                          &#(((#(###############(#############%&((((############(###############(###############(#######%&@&%%%%%%%%%%%%%%%%%%%%%%%%%%
                           @####(#######(#######(#######(##(%&(((#(#####(#######(#######(#######(#######(#######((##%@&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                            @#(((###############(#######((#@#(##(###############(###############(##############(%@&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                             &(#(###(###(###(###(###(##((#&###(((###(###(###(###(###(###(###(###(###(###(#####@&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                             /%((###############(#####((#&######(###############(###############(#(########&&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                              &#(#######(#######(######(%%##((##(#######(#######(#######(#######(####(((#@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                               @################(######(%%##((##((##############(###############(##(###@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                &##((#(#(#(#(#(#(#(#(#(#(@##(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(#(##@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                (%(((###########(#######(#&&##(#(###############(################(#@&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                 ##(####(#######(#######(#(#%&#((#######(#######(#######(#######(&@%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                  /%(((###(#####(########(###(#&%###############(############((#@&%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/

/// @title Pages NFT
/// @author FrankieIsLost <[email protected]>
/// @author transmissions11 <[email protected]>
/// @notice Pages is an ERC721 that can hold custom art.
contract Pages is PagesERC721, LogisticToLinearVRGDA {
    using LibString for uint256;

    /*//////////////////////////////////////////////////////////////
                                ADDRESSES
    //////////////////////////////////////////////////////////////*/

    /// @notice The address of the goo ERC20 token contract.
    Goo public immutable goo;

    /// @notice The address which receives pages reserved for the community.
    address public immutable community;

    /*//////////////////////////////////////////////////////////////
                                  URIS
    //////////////////////////////////////////////////////////////*/

    /// @notice Base URI for minted pages.
    string public BASE_URI;

    /*//////////////////////////////////////////////////////////////
                            VRGDA INPUT STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice Timestamp for the start of the VRGDA mint.
    uint256 public immutable mintStart;

    /// @notice Id of the most recently minted page.
    /// @dev Will be 0 if no pages have been minted yet.
    uint128 public currentId;

    /*//////////////////////////////////////////////////////////////
                          COMMUNITY PAGES STATE
    //////////////////////////////////////////////////////////////*/

    /// @notice The number of pages minted to the community reserve.
    uint128 public numMintedForCommunity;

    /*//////////////////////////////////////////////////////////////
                            PRICING CONSTANTS
    //////////////////////////////////////////////////////////////*/

    /// @dev The day the switch from a logistic to translated linear VRGDA is targeted to occur.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal constant SWITCH_DAY_WAD = 233e18;

    /// @notice The minimum amount of pages that must be sold for the VRGDA issuance
    /// schedule to switch from logistic to the "post switch" translated linear formula.
    /// @dev Computed off-chain by plugging SWITCH_DAY_WAD into the uninverted pacing formula.
    /// @dev Represented as an 18 decimal fixed point number.
    int256 internal constant SOLD_BY_SWITCH_WAD = 8336.760939794622713006e18;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event PagePurchased(address indexed user, uint256 indexed pageId, uint256 price);

    event CommunityPagesMinted(address indexed user, uint256 lastMintedPageId, uint256 numPages);

    /*//////////////////////////////////////////////////////////////
                                 ERRORS
    //////////////////////////////////////////////////////////////*/

    error ReserveImbalance();

    error PriceExceededMax(uint256 currentPrice);

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

    /// @notice Sets VRGDA parameters, mint start, relevant addresses, and base URI.
    /// @param _mintStart Timestamp for the start of the VRGDA mint.
    /// @param _goo Address of the Goo contract.
    /// @param _community Address of the community reserve.
    /// @param _artGobblers Address of the ArtGobblers contract.
    /// @param _baseUri Base URI for token metadata.
    constructor(
        // Mint config:
        uint256 _mintStart,
        // Addresses:
        Goo _goo,
        address _community,
        ArtGobblers _artGobblers,
        // URIs:
        string memory _baseUri
    )
        PagesERC721(_artGobblers, "Pages", "PAGE")
        LogisticToLinearVRGDA(
            4.2069e18, // Target price.
            0.31e18, // Price decay percent.
            9000e18, // Logistic asymptote.
            0.014e18, // Logistic time scale.
            SOLD_BY_SWITCH_WAD, // Sold by switch.
            SWITCH_DAY_WAD, // Target switch day.
            9e18 // Pages to target per day.
        )
    {
        mintStart = _mintStart;

        goo = _goo;

        community = _community;

        BASE_URI = _baseUri;
    }

    /*//////////////////////////////////////////////////////////////
                              MINTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Mint a page with goo, burning the cost.
    /// @param maxPrice Maximum price to pay to mint the page.
    /// @param useVirtualBalance Whether the cost is paid from the
    /// user's virtual goo balance, or from their ERC20 goo balance.
    /// @return pageId The id of the page that was minted.
    function mintFromGoo(uint256 maxPrice, bool useVirtualBalance) external returns (uint256 pageId) {
        // Will revert if prior to mint start.
        uint256 currentPrice = pagePrice();

        // If the current price is above the user's specified max, revert.
        if (currentPrice > maxPrice) revert PriceExceededMax(currentPrice);

        // Decrement the user's goo balance by the current
        // price, either from virtual balance or ERC20 balance.
        useVirtualBalance
            ? artGobblers.burnGooForPages(msg.sender, currentPrice)
            : goo.burnForPages(msg.sender, currentPrice);

        unchecked {
            emit PagePurchased(msg.sender, pageId = ++currentId, currentPrice);

            _mint(msg.sender, pageId);
        }
    }

    /// @notice Calculate the mint cost of a page.
    /// @dev If the number of sales is below a pre-defined threshold, we use the
    /// VRGDA pricing algorithm, otherwise we use the post-switch pricing formula.
    /// @dev Reverts due to underflow if minting hasn't started yet. Done to save gas.
    function pagePrice() public view returns (uint256) {
        // We need checked math here to cause overflow
        // before minting has begun, preventing mints.
        uint256 timeSinceStart = block.timestamp - mintStart;

        unchecked {
            // The number of pages minted for the community reserve
            // should never exceed 10% of the total supply of pages.
            return getVRGDAPrice(toDaysWadUnsafe(timeSinceStart), currentId - numMintedForCommunity);
        }
    }

    /*//////////////////////////////////////////////////////////////
                      COMMUNITY PAGES MINTING LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Mint a number of pages to the community reserve.
    /// @param numPages The number of pages to mint to the reserve.
    /// @dev Pages minted to the reserve cannot comprise more than 10% of the sum of the
    /// supply of goo minted pages and the supply of pages minted to the community reserve.
    function mintCommunityPages(uint256 numPages) external returns (uint256 lastMintedPageId) {
        unchecked {
            // Optimistically increment numMintedForCommunity, may be reverted below.
            // Overflow in this calculation is possible but numPages would have to be so
            // large that it would cause the loop in _batchMint to run out of gas quickly.
            uint256 newNumMintedForCommunity = numMintedForCommunity += uint128(numPages);

            // Ensure that after this mint pages minted to the community reserve won't comprise more than
            // 10% of the new total page supply. currentId is equivalent to the current total supply of pages.
            if (newNumMintedForCommunity > ((lastMintedPageId = currentId) + numPages) / 10) revert ReserveImbalance();

            // Mint the pages to the community reserve and update lastMintedPageId once minting is complete.
            lastMintedPageId = _batchMint(community, numPages, lastMintedPageId);

            currentId = uint128(lastMintedPageId); // Update currentId with the last minted page id.

            emit CommunityPagesMinted(msg.sender, lastMintedPageId, numPages);
        }
    }

    /*//////////////////////////////////////////////////////////////
                             TOKEN URI LOGIC
    //////////////////////////////////////////////////////////////*/

    /// @notice Returns a page's URI if it has been minted.
    /// @param pageId The id of the page to get the URI for.
    function tokenURI(uint256 pageId) public view virtual override returns (string memory) {
        if (pageId == 0 || pageId > currentId) revert("NOT_MINTED");

        return string.concat(BASE_URI, pageId.toString());
    }
}

File 9 of 27 : RandProvider.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title Randomness Provider Interface.
/// @author FrankieIsLost <[email protected]>
/// @author transmissions11 <[email protected]>
/// @notice Generic asynchronous randomness provider interface.
interface RandProvider {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event RandomBytesRequested(bytes32 requestId);
    event RandomBytesReturned(bytes32 requestId, uint256 randomness);

    /*//////////////////////////////////////////////////////////////
                                FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /// @dev Request random bytes from the randomness provider.
    function requestRandomBytes() external returns (bytes32 requestId);
}

File 10 of 27 : GobblersERC721.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol";

/// @notice ERC721 implementation optimized for ArtGobblers by packing balanceOf/ownerOf with user/attribute data.
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract GobblersERC721 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) external view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                         GOBBLERS/ERC721 STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice Struct holding gobbler data.
    struct GobblerData {
        // The current owner of the gobbler.
        address owner;
        // Index of token after shuffle.
        uint64 idx;
        // Multiple on goo issuance.
        uint32 emissionMultiple;
    }

    /// @notice Maps gobbler ids to their data.
    mapping(uint256 => GobblerData) public getGobblerData;

    /// @notice Struct holding data relevant to each user's account.
    struct UserData {
        // The total number of gobblers currently owned by the user.
        uint32 gobblersOwned;
        // The sum of the multiples of all gobblers the user holds.
        uint32 emissionMultiple;
        // User's goo balance at time of last checkpointing.
        uint128 lastBalance;
        // Timestamp of the last goo balance checkpoint.
        uint64 lastTimestamp;
    }

    /// @notice Maps user addresses to their account data.
    mapping(address => UserData) public getUserData;

    function ownerOf(uint256 id) external view returns (address owner) {
        require((owner = getGobblerData[id].owner) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) external view returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return getUserData[owner].gobblersOwned;
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

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

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) external {
        address owner = getGobblerData[id].owner;

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) external {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual;

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) external {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) external {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                           INTERNAL MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal {
        // Does not check if the token was already minted or the recipient is address(0)
        // because ArtGobblers.sol manages its ids in such a way that it ensures it won't
        // double mint and will only mint to safe addresses or msg.sender who cannot be zero.

        unchecked {
            ++getUserData[to].gobblersOwned;
        }

        getGobblerData[id].owner = to;

        emit Transfer(address(0), to, id);
    }

    function _batchMint(
        address to,
        uint256 amount,
        uint256 lastMintedId
    ) internal returns (uint256) {
        // Doesn't check if the tokens were already minted or the recipient is address(0)
        // because ArtGobblers.sol manages its ids in such a way that it ensures it won't
        // double mint and will only mint to safe addresses or msg.sender who cannot be zero.

        unchecked {
            getUserData[to].gobblersOwned += uint32(amount);

            for (uint256 i = 0; i < amount; ++i) {
                getGobblerData[++lastMintedId].owner = to;

                emit Transfer(address(0), to, lastMintedId);
            }
        }

        return lastMintedId;
    }
}

File 11 of 27 : PagesERC721.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import {ERC721TokenReceiver} from "solmate/tokens/ERC721.sol";
import {ArtGobblers} from "../../ArtGobblers.sol";

/// @notice ERC721 implementation optimized for Pages by pre-approving them to the ArtGobblers contract.
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract PagesERC721 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) external view virtual returns (string memory);

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

    ArtGobblers public immutable artGobblers;

    constructor(
        ArtGobblers _artGobblers,
        string memory _name,
        string memory _symbol
    ) {
        name = _name;
        symbol = _symbol;
        artGobblers = _artGobblers;
    }

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) external view returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) external view returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) internal _isApprovedForAll;

    function isApprovedForAll(address owner, address operator) public view returns (bool isApproved) {
        if (operator == address(artGobblers)) return true; // Skip approvals for the ArtGobblers contract.

        return _isApprovedForAll[owner][operator];
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) external {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) external {
        _isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public {
        require(from == _ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || isApprovedForAll(from, msg.sender) || msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) external {
        transferFrom(from, to, id);

        if (to.code.length != 0)
            require(
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                    ERC721TokenReceiver.onERC721Received.selector,
                "UNSAFE_RECIPIENT"
            );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) external {
        transferFrom(from, to, id);

        if (to.code.length != 0)
            require(
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                    ERC721TokenReceiver.onERC721Received.selector,
                "UNSAFE_RECIPIENT"
            );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                           INTERNAL MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal {
        // Does not check the token has not been already minted
        // or is being minted to address(0) because ids in Pages.sol
        // are set using a monotonically increasing counter and only
        // minted to safe addresses or msg.sender who cannot be zero.

        // Counter overflow is incredibly unrealistic.
        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _batchMint(
        address to,
        uint256 amount,
        uint256 lastMintedId
    ) internal returns (uint256) {
        // Doesn't check if the tokens were already minted or the recipient is address(0)
        // because Pages.sol manages its ids in a way that it ensures it won't double
        // mint and will only mint to safe addresses or msg.sender who cannot be zero.

        unchecked {
            _balanceOf[to] += amount;

            for (uint256 i = 0; i < amount; ++i) {
                _ownerOf[++lastMintedId] = to;

                emit Transfer(address(0), to, lastMintedId);
            }
        }

        return lastMintedId;
    }
}

File 12 of 27 : Owned.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

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

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

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

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}

File 13 of 27 : ERC1155.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Minimalist and gas efficient standard ERC1155 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event TransferSingle(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256 id,
        uint256 amount
    );

    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] amounts
    );

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    event URI(string value, uint256 indexed id);

    /*//////////////////////////////////////////////////////////////
                             ERC1155 STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(address => mapping(uint256 => uint256)) public balanceOf;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

    /*//////////////////////////////////////////////////////////////
                             METADATA LOGIC
    //////////////////////////////////////////////////////////////*/

    function uri(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                              ERC1155 LOGIC
    //////////////////////////////////////////////////////////////*/

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes calldata data
    ) public virtual {
        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        balanceOf[from][id] -= amount;
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, from, to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, from, id, amount, data) ==
                    ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) public virtual {
        require(ids.length == amounts.length, "LENGTH_MISMATCH");

        require(msg.sender == from || isApprovedForAll[from][msg.sender], "NOT_AUTHORIZED");

        // Storing these outside the loop saves ~15 gas per iteration.
        uint256 id;
        uint256 amount;

        for (uint256 i = 0; i < ids.length; ) {
            id = ids[i];
            amount = amounts[i];

            balanceOf[from][id] -= amount;
            balanceOf[to][id] += amount;

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, from, ids, amounts, data) ==
                    ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function balanceOfBatch(address[] calldata owners, uint256[] calldata ids)
        public
        view
        virtual
        returns (uint256[] memory balances)
    {
        require(owners.length == ids.length, "LENGTH_MISMATCH");

        balances = new uint256[](owners.length);

        // Unchecked because the only math done is incrementing
        // the array index counter which cannot possibly overflow.
        unchecked {
            for (uint256 i = 0; i < owners.length; ++i) {
                balances[i] = balanceOf[owners[i]][ids[i]];
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0xd9b67a26 || // ERC165 Interface ID for ERC1155
            interfaceId == 0x0e89341c; // ERC165 Interface ID for ERC1155MetadataURI
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        balanceOf[to][id] += amount;

        emit TransferSingle(msg.sender, address(0), to, id, amount);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155Received(msg.sender, address(0), id, amount, data) ==
                    ERC1155TokenReceiver.onERC1155Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchMint(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[to][ids[i]] += amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, address(0), to, ids, amounts);

        require(
            to.code.length == 0
                ? to != address(0)
                : ERC1155TokenReceiver(to).onERC1155BatchReceived(msg.sender, address(0), ids, amounts, data) ==
                    ERC1155TokenReceiver.onERC1155BatchReceived.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _batchBurn(
        address from,
        uint256[] memory ids,
        uint256[] memory amounts
    ) internal virtual {
        uint256 idsLength = ids.length; // Saves MLOADs.

        require(idsLength == amounts.length, "LENGTH_MISMATCH");

        for (uint256 i = 0; i < idsLength; ) {
            balanceOf[from][ids[i]] -= amounts[i];

            // An array can't have a total length
            // larger than the max uint256 value.
            unchecked {
                ++i;
            }
        }

        emit TransferBatch(msg.sender, from, address(0), ids, amounts);
    }

    function _burn(
        address from,
        uint256 id,
        uint256 amount
    ) internal virtual {
        balanceOf[from][id] -= amount;

        emit TransferSingle(msg.sender, from, address(0), id, amount);
    }
}

/// @notice A generic interface for a contract which properly accepts ERC1155 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC1155.sol)
abstract contract ERC1155TokenReceiver {
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiver.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] calldata,
        uint256[] calldata,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC1155TokenReceiver.onERC1155BatchReceived.selector;
    }
}

File 14 of 27 : ERC20.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 amount);

    event Approval(address indexed owner, address indexed spender, uint256 amount);

    /*//////////////////////////////////////////////////////////////
                            METADATA STORAGE
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    uint8 public immutable decimals;

    /*//////////////////////////////////////////////////////////////
                              ERC20 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 public totalSupply;

    mapping(address => uint256) public balanceOf;

    mapping(address => mapping(address => uint256)) public allowance;

    /*//////////////////////////////////////////////////////////////
                            EIP-2612 STORAGE
    //////////////////////////////////////////////////////////////*/

    uint256 internal immutable INITIAL_CHAIN_ID;

    bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;

    mapping(address => uint256) public nonces;

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

    constructor(
        string memory _name,
        string memory _symbol,
        uint8 _decimals
    ) {
        name = _name;
        symbol = _symbol;
        decimals = _decimals;

        INITIAL_CHAIN_ID = block.chainid;
        INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
    }

    /*//////////////////////////////////////////////////////////////
                               ERC20 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 amount) public virtual returns (bool) {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);

        return true;
    }

    function transfer(address to, uint256 amount) public virtual returns (bool) {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);

        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);

        return true;
    }

    /*//////////////////////////////////////////////////////////////
                             EIP-2612 LOGIC
    //////////////////////////////////////////////////////////////*/

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");

        // Unchecked because the only math done is incrementing
        // the owner's nonce which cannot realistically overflow.
        unchecked {
            address recoveredAddress = ecrecover(
                keccak256(
                    abi.encodePacked(
                        "\x19\x01",
                        DOMAIN_SEPARATOR(),
                        keccak256(
                            abi.encode(
                                keccak256(
                                    "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
                                ),
                                owner,
                                spender,
                                value,
                                nonces[owner]++,
                                deadline
                            )
                        )
                    )
                ),
                v,
                r,
                s
            );

            require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");

            allowance[recoveredAddress][spender] = value;
        }

        emit Approval(owner, spender, value);
    }

    function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
        return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
    }

    function computeDomainSeparator() internal view virtual returns (bytes32) {
        return
            keccak256(
                abi.encode(
                    keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
                    keccak256(bytes(name)),
                    keccak256("1"),
                    block.chainid,
                    address(this)
                )
            );
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 amount) internal virtual {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    function _burn(address from, uint256 amount) internal virtual {
        balanceOf[from] -= amount;

        // Cannot underflow because a user's balance
        // will never be larger than the total supply.
        unchecked {
            totalSupply -= amount;
        }

        emit Transfer(from, address(0), amount);
    }
}

File 15 of 27 : ERC721.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Modern, minimalist, and gas efficient ERC-721 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721 {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Transfer(address indexed from, address indexed to, uint256 indexed id);

    event Approval(address indexed owner, address indexed spender, uint256 indexed id);

    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /*//////////////////////////////////////////////////////////////
                         METADATA STORAGE/LOGIC
    //////////////////////////////////////////////////////////////*/

    string public name;

    string public symbol;

    function tokenURI(uint256 id) public view virtual returns (string memory);

    /*//////////////////////////////////////////////////////////////
                      ERC721 BALANCE/OWNER STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) internal _ownerOf;

    mapping(address => uint256) internal _balanceOf;

    function ownerOf(uint256 id) public view virtual returns (address owner) {
        require((owner = _ownerOf[id]) != address(0), "NOT_MINTED");
    }

    function balanceOf(address owner) public view virtual returns (uint256) {
        require(owner != address(0), "ZERO_ADDRESS");

        return _balanceOf[owner];
    }

    /*//////////////////////////////////////////////////////////////
                         ERC721 APPROVAL STORAGE
    //////////////////////////////////////////////////////////////*/

    mapping(uint256 => address) public getApproved;

    mapping(address => mapping(address => bool)) public isApprovedForAll;

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

    constructor(string memory _name, string memory _symbol) {
        name = _name;
        symbol = _symbol;
    }

    /*//////////////////////////////////////////////////////////////
                              ERC721 LOGIC
    //////////////////////////////////////////////////////////////*/

    function approve(address spender, uint256 id) public virtual {
        address owner = _ownerOf[id];

        require(msg.sender == owner || isApprovedForAll[owner][msg.sender], "NOT_AUTHORIZED");

        getApproved[id] = spender;

        emit Approval(owner, spender, id);
    }

    function setApprovalForAll(address operator, bool approved) public virtual {
        isApprovedForAll[msg.sender][operator] = approved;

        emit ApprovalForAll(msg.sender, operator, approved);
    }

    function transferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        require(from == _ownerOf[id], "WRONG_FROM");

        require(to != address(0), "INVALID_RECIPIENT");

        require(
            msg.sender == from || isApprovedForAll[from][msg.sender] || msg.sender == getApproved[id],
            "NOT_AUTHORIZED"
        );

        // Underflow of the sender's balance is impossible because we check for
        // ownership above and the recipient's balance can't realistically overflow.
        unchecked {
            _balanceOf[from]--;

            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        delete getApproved[id];

        emit Transfer(from, to, id);
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        bytes calldata data
    ) public virtual {
        transferFrom(from, to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    /*//////////////////////////////////////////////////////////////
                              ERC165 LOGIC
    //////////////////////////////////////////////////////////////*/

    function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
        return
            interfaceId == 0x01ffc9a7 || // ERC165 Interface ID for ERC165
            interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
            interfaceId == 0x5b5e139f; // ERC165 Interface ID for ERC721Metadata
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL MINT/BURN LOGIC
    //////////////////////////////////////////////////////////////*/

    function _mint(address to, uint256 id) internal virtual {
        require(to != address(0), "INVALID_RECIPIENT");

        require(_ownerOf[id] == address(0), "ALREADY_MINTED");

        // Counter overflow is incredibly unrealistic.
        unchecked {
            _balanceOf[to]++;
        }

        _ownerOf[id] = to;

        emit Transfer(address(0), to, id);
    }

    function _burn(uint256 id) internal virtual {
        address owner = _ownerOf[id];

        require(owner != address(0), "NOT_MINTED");

        // Ownership check above ensures no underflow.
        unchecked {
            _balanceOf[owner]--;
        }

        delete _ownerOf[id];

        delete getApproved[id];

        emit Transfer(owner, address(0), id);
    }

    /*//////////////////////////////////////////////////////////////
                        INTERNAL SAFE MINT LOGIC
    //////////////////////////////////////////////////////////////*/

    function _safeMint(address to, uint256 id) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, "") ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }

    function _safeMint(
        address to,
        uint256 id,
        bytes memory data
    ) internal virtual {
        _mint(to, id);

        require(
            to.code.length == 0 ||
                ERC721TokenReceiver(to).onERC721Received(msg.sender, address(0), id, data) ==
                ERC721TokenReceiver.onERC721Received.selector,
            "UNSAFE_RECIPIENT"
        );
    }
}

/// @notice A generic interface for a contract which properly accepts ERC721 tokens.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC721.sol)
abstract contract ERC721TokenReceiver {
    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external virtual returns (bytes4) {
        return ERC721TokenReceiver.onERC721Received.selector;
    }
}

File 16 of 27 : FixedPointMathLib.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Arithmetic library with operations for fixed-point numbers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol)
/// @author Inspired by USM (https://github.com/usmfum/USM/blob/master/contracts/WadMath.sol)
library FixedPointMathLib {
    /*//////////////////////////////////////////////////////////////
                    SIMPLIFIED FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    uint256 internal constant MAX_UINT256 = 2**256 - 1;

    uint256 internal constant WAD = 1e18; // The scalar of ETH and most ERC20s.

    function mulWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, y, WAD); // Equivalent to (x * y) / WAD rounded down.
    }

    function mulWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, y, WAD); // Equivalent to (x * y) / WAD rounded up.
    }

    function divWadDown(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivDown(x, WAD, y); // Equivalent to (x * WAD) / y rounded down.
    }

    function divWadUp(uint256 x, uint256 y) internal pure returns (uint256) {
        return mulDivUp(x, WAD, y); // Equivalent to (x * WAD) / y rounded up.
    }

    /*//////////////////////////////////////////////////////////////
                    LOW LEVEL FIXED POINT OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function mulDivDown(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // Divide x * y by the denominator.
            z := div(mul(x, y), denominator)
        }
    }

    function mulDivUp(
        uint256 x,
        uint256 y,
        uint256 denominator
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Equivalent to require(denominator != 0 && (y == 0 || x <= type(uint256).max / y))
            if iszero(mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))) {
                revert(0, 0)
            }

            // If x * y modulo the denominator is strictly greater than 0,
            // 1 is added to round up the division of x * y by the denominator.
            z := add(gt(mod(mul(x, y), denominator), 0), div(mul(x, y), denominator))
        }
    }

    function rpow(
        uint256 x,
        uint256 n,
        uint256 scalar
    ) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            switch x
            case 0 {
                switch n
                case 0 {
                    // 0 ** 0 = 1
                    z := scalar
                }
                default {
                    // 0 ** n = 0
                    z := 0
                }
            }
            default {
                switch mod(n, 2)
                case 0 {
                    // If n is even, store scalar in z for now.
                    z := scalar
                }
                default {
                    // If n is odd, store x in z for now.
                    z := x
                }

                // Shifting right by 1 is like dividing by 2.
                let half := shr(1, scalar)

                for {
                    // Shift n right by 1 before looping to halve it.
                    n := shr(1, n)
                } n {
                    // Shift n right by 1 each iteration to halve it.
                    n := shr(1, n)
                } {
                    // Revert immediately if x ** 2 would overflow.
                    // Equivalent to iszero(eq(div(xx, x), x)) here.
                    if shr(128, x) {
                        revert(0, 0)
                    }

                    // Store x squared.
                    let xx := mul(x, x)

                    // Round to the nearest number.
                    let xxRound := add(xx, half)

                    // Revert if xx + half overflowed.
                    if lt(xxRound, xx) {
                        revert(0, 0)
                    }

                    // Set x to scaled xxRound.
                    x := div(xxRound, scalar)

                    // If n is even:
                    if mod(n, 2) {
                        // Compute z * x.
                        let zx := mul(z, x)

                        // If z * x overflowed:
                        if iszero(eq(div(zx, x), z)) {
                            // Revert if x is non-zero.
                            if iszero(iszero(x)) {
                                revert(0, 0)
                            }
                        }

                        // Round to the nearest number.
                        let zxRound := add(zx, half)

                        // Revert if zx + half overflowed.
                        if lt(zxRound, zx) {
                            revert(0, 0)
                        }

                        // Return properly scaled zxRound.
                        z := div(zxRound, scalar)
                    }
                }
            }
        }
    }

    /*//////////////////////////////////////////////////////////////
                        GENERAL NUMBER UTILITIES
    //////////////////////////////////////////////////////////////*/

    function sqrt(uint256 x) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            let y := x // We start y at x, which will help us make our initial estimate.

            z := 181 // The "correct" value is 1, but this saves a multiplication later.

            // This segment is to get a reasonable initial estimate for the Babylonian method. With a bad
            // start, the correct # of bits increases ~linearly each iteration instead of ~quadratically.

            // We check y >= 2^(k + 8) but shift right by k bits
            // each branch to ensure that if x >= 256, then y >= 256.
            if iszero(lt(y, 0x10000000000000000000000000000000000)) {
                y := shr(128, y)
                z := shl(64, z)
            }
            if iszero(lt(y, 0x1000000000000000000)) {
                y := shr(64, y)
                z := shl(32, z)
            }
            if iszero(lt(y, 0x10000000000)) {
                y := shr(32, y)
                z := shl(16, z)
            }
            if iszero(lt(y, 0x1000000)) {
                y := shr(16, y)
                z := shl(8, z)
            }

            // Goal was to get z*z*y within a small factor of x. More iterations could
            // get y in a tighter range. Currently, we will have y in [256, 256*2^16).
            // We ensured y >= 256 so that the relative difference between y and y+1 is small.
            // That's not possible if x < 256 but we can just verify those cases exhaustively.

            // Now, z*z*y <= x < z*z*(y+1), and y <= 2^(16+8), and either y >= 256, or x < 256.
            // Correctness can be checked exhaustively for x < 256, so we assume y >= 256.
            // Then z*sqrt(y) is within sqrt(257)/sqrt(256) of sqrt(x), or about 20bps.

            // For s in the range [1/256, 256], the estimate f(s) = (181/1024) * (s+1) is in the range
            // (1/2.84 * sqrt(s), 2.84 * sqrt(s)), with largest error when s = 1 and when s = 256 or 1/256.

            // Since y is in [256, 256*2^16), let a = y/65536, so that a is in [1/256, 256). Then we can estimate
            // sqrt(y) using sqrt(65536) * 181/1024 * (a + 1) = 181/4 * (y + 65536)/65536 = 181 * (y + 65536)/2^18.

            // There is no overflow risk here since y < 2^136 after the first branch above.
            z := shr(18, mul(z, add(y, 65536))) // A mul() is saved from starting z at 181.

            // Given the worst case multiplicative error of 2.84 above, 7 iterations should be enough.
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))
            z := shr(1, add(z, div(x, z)))

            // If x+1 is a perfect square, the Babylonian method cycles between
            // floor(sqrt(x)) and ceil(sqrt(x)). This statement ensures we return floor.
            // See: https://en.wikipedia.org/wiki/Integer_square_root#Using_only_integer_division
            // Since the ceil is rare, we save gas on the assignment and repeat division in the rare case.
            // If you don't care whether the floor or ceil square root is returned, you can remove this statement.
            z := sub(z, lt(div(x, z), z))
        }
    }

    function unsafeMod(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Mod x by y. Note this will return
            // 0 instead of reverting if y is zero.
            z := mod(x, y)
        }
    }

    function unsafeDiv(uint256 x, uint256 y) internal pure returns (uint256 r) {
        /// @solidity memory-safe-assembly
        assembly {
            // Divide x by y. Note this will return
            // 0 instead of reverting if y is zero.
            r := div(x, y)
        }
    }

    function unsafeDivUp(uint256 x, uint256 y) internal pure returns (uint256 z) {
        /// @solidity memory-safe-assembly
        assembly {
            // Add 1 to x * y if x % y > 0. Note this will
            // return 0 instead of reverting if y is zero.
            z := add(gt(mod(x, y), 0), div(x, y))
        }
    }
}

File 17 of 27 : LibString.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Efficient library for creating string representations of integers.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/LibString.sol)
/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol)
library LibString {
    function toString(uint256 value) internal pure returns (string memory str) {
        /// @solidity memory-safe-assembly
        assembly {
            // The maximum value of a uint256 contains 78 digits (1 byte per digit), but we allocate 160 bytes
            // to keep the free memory pointer word aligned. We'll need 1 word for the length, 1 word for the
            // trailing zeros padding, and 3 other words for a max of 78 digits. In total: 5 * 32 = 160 bytes.
            let newFreeMemoryPointer := add(mload(0x40), 160)

            // Update the free memory pointer to avoid overriding our string.
            mstore(0x40, newFreeMemoryPointer)

            // Assign str to the end of the zone of newly allocated memory.
            str := sub(newFreeMemoryPointer, 32)

            // Clean the last word of memory it may not be overwritten.
            mstore(str, 0)

            // Cache the end of the memory to calculate the length later.
            let end := str

            // We write the string from rightmost digit to leftmost digit.
            // The following is essentially a do-while loop that also handles the zero case.
            // prettier-ignore
            for { let temp := value } 1 {} {
                // Move the pointer 1 byte to the left.
                str := sub(str, 1)

                // Write the character to the pointer.
                // The ASCII index of the '0' character is 48.
                mstore8(str, add(48, mod(temp, 10)))

                // Keep dividing temp until zero.
                temp := div(temp, 10)

                 // prettier-ignore
                if iszero(temp) { break }
            }

            // Compute and cache the final total length of the string.
            let length := sub(end, str)

            // Move the pointer 32 bytes leftwards to make room for the length.
            str := sub(str, 32)

            // Store the string's length at the start of memory allocated for our string.
            mstore(str, length)
        }
    }
}

File 18 of 27 : MerkleProofLib.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Gas optimized merkle proof verification library.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
library MerkleProofLib {
    function verify(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32 leaf
    ) internal pure returns (bool isValid) {
        /// @solidity memory-safe-assembly
        assembly {
            if proof.length {
                // Left shifting by 5 is like multiplying by 32.
                let end := add(proof.offset, shl(5, proof.length))

                // Initialize offset to the offset of the proof in calldata.
                let offset := proof.offset

                // Iterate over proof elements to compute root hash.
                // prettier-ignore
                for {} 1 {} {
                    // Slot where the leaf should be put in scratch space. If
                    // leaf > calldataload(offset): slot 32, otherwise: slot 0.
                    let leafSlot := shl(5, gt(leaf, calldataload(offset)))

                    // Store elements to hash contiguously in scratch space.
                    // The xor puts calldataload(offset) in whichever slot leaf
                    // is not occupying, so 0 if leafSlot is 32, and 32 otherwise.
                    mstore(leafSlot, leaf)
                    mstore(xor(leafSlot, 32), calldataload(offset))

                    // Reuse leaf to store the hash to reduce stack operations.
                    leaf := keccak256(0, 64) // Hash both slots of scratch space.

                    offset := add(offset, 32) // Shift 1 word per cycle.

                    // prettier-ignore
                    if iszero(lt(offset, end)) { break }
                }
            }

            isValid := eq(leaf, root) // The proof is valid if the roots match.
        }
    }
}

File 19 of 27 : ReentrancyGuard.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
    uint256 private locked = 1;

    modifier nonReentrant() virtual {
        require(locked == 1, "REENTRANCY");

        locked = 2;

        _;

        locked = 1;
    }
}

File 20 of 27 : SafeTransferLib.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

import {ERC20} from "../tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        require(success, "ETH_TRANSFER_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        require(success, "TRANSFER_FROM_FAILED");
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "TRANSFER_FAILED");
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        require(success, "APPROVE_FAILED");
    }
}

File 21 of 27 : SignedWadMath.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @notice Signed 18 decimal fixed point (wad) arithmetic library.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SignedWadMath.sol)
/// @author Modified from Remco Bloemen (https://xn--2-umb.com/22/exp-ln/index.html)

/// @dev Will not revert on overflow, only use where overflow is not possible.
function toWadUnsafe(uint256 x) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Multiply x by 1e18.
        r := mul(x, 1000000000000000000)
    }
}

/// @dev Takes an integer amount of seconds and converts it to a wad amount of days.
/// @dev Will not revert on overflow, only use where overflow is not possible.
/// @dev Not meant for negative second amounts, it assumes x is positive.
function toDaysWadUnsafe(uint256 x) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Multiply x by 1e18 and then divide it by 86400.
        r := div(mul(x, 1000000000000000000), 86400)
    }
}

/// @dev Takes a wad amount of days and converts it to an integer amount of seconds.
/// @dev Will not revert on overflow, only use where overflow is not possible.
/// @dev Not meant for negative day amounts, it assumes x is positive.
function fromDaysWadUnsafe(int256 x) pure returns (uint256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Multiply x by 86400 and then divide it by 1e18.
        r := div(mul(x, 86400), 1000000000000000000)
    }
}

/// @dev Will not revert on overflow, only use where overflow is not possible.
function unsafeWadMul(int256 x, int256 y) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Multiply x by y and divide by 1e18.
        r := sdiv(mul(x, y), 1000000000000000000)
    }
}

/// @dev Will return 0 instead of reverting if y is zero and will
/// not revert on overflow, only use where overflow is not possible.
function unsafeWadDiv(int256 x, int256 y) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Multiply x by 1e18 and divide it by y.
        r := sdiv(mul(x, 1000000000000000000), y)
    }
}

function wadMul(int256 x, int256 y) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Store x * y in r for now.
        r := mul(x, y)

        // Equivalent to require(x == 0 || (x * y) / x == y)
        if iszero(or(iszero(x), eq(sdiv(r, x), y))) {
            revert(0, 0)
        }

        // Scale the result down by 1e18.
        r := sdiv(r, 1000000000000000000)
    }
}

function wadDiv(int256 x, int256 y) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Store x * 1e18 in r for now.
        r := mul(x, 1000000000000000000)

        // Equivalent to require(y != 0 && ((x * 1e18) / 1e18 == x))
        if iszero(and(iszero(iszero(y)), eq(sdiv(r, 1000000000000000000), x))) {
            revert(0, 0)
        }

        // Divide r by y.
        r := sdiv(r, y)
    }
}

function wadExp(int256 x) pure returns (int256 r) {
    unchecked {
        // When the result is < 0.5 we return zero. This happens when
        // x <= floor(log(0.5e18) * 1e18) ~ -42e18
        if (x <= -42139678854452767551) return 0;

        // When the result is > (2**255 - 1) / 1e18 we can not represent it as an
        // int. This happens when x >= floor(log((2**255 - 1) / 1e18) * 1e18) ~ 135.
        if (x >= 135305999368893231589) revert("EXP_OVERFLOW");

        // x is now in the range (-42, 136) * 1e18. Convert to (-42, 136) * 2**96
        // for more intermediate precision and a binary basis. This base conversion
        // is a multiplication by 1e18 / 2**96 = 5**18 / 2**78.
        x = (x << 78) / 5**18;

        // Reduce range of x to (-½ ln 2, ½ ln 2) * 2**96 by factoring out powers
        // of two such that exp(x) = exp(x') * 2**k, where k is an integer.
        // Solving this gives k = round(x / log(2)) and x' = x - k * log(2).
        int256 k = ((x << 96) / 54916777467707473351141471128 + 2**95) >> 96;
        x = x - k * 54916777467707473351141471128;

        // k is in the range [-61, 195].

        // Evaluate using a (6, 7)-term rational approximation.
        // p is made monic, we'll multiply by a scale factor later.
        int256 y = x + 1346386616545796478920950773328;
        y = ((y * x) >> 96) + 57155421227552351082224309758442;
        int256 p = y + x - 94201549194550492254356042504812;
        p = ((p * y) >> 96) + 28719021644029726153956944680412240;
        p = p * x + (4385272521454847904659076985693276 << 96);

        // We leave p in 2**192 basis so we don't need to scale it back up for the division.
        int256 q = x - 2855989394907223263936484059900;
        q = ((q * x) >> 96) + 50020603652535783019961831881945;
        q = ((q * x) >> 96) - 533845033583426703283633433725380;
        q = ((q * x) >> 96) + 3604857256930695427073651918091429;
        q = ((q * x) >> 96) - 14423608567350463180887372962807573;
        q = ((q * x) >> 96) + 26449188498355588339934803723976023;

        /// @solidity memory-safe-assembly
        assembly {
            // Div in assembly because solidity adds a zero check despite the unchecked.
            // The q polynomial won't have zeros in the domain as all its roots are complex.
            // No scaling is necessary because p is already 2**96 too large.
            r := sdiv(p, q)
        }

        // r should be in the range (0.09, 0.25) * 2**96.

        // We now need to multiply r by:
        // * the scale factor s = ~6.031367120.
        // * the 2**k factor from the range reduction.
        // * the 1e18 / 2**96 factor for base conversion.
        // We do this all at once, with an intermediate result in 2**213
        // basis, so the final right shift is always by a positive amount.
        r = int256((uint256(r) * 3822833074963236453042738258902158003155416615667) >> uint256(195 - k));
    }
}

function wadLn(int256 x) pure returns (int256 r) {
    unchecked {
        require(x > 0, "UNDEFINED");

        // We want to convert x from 10**18 fixed point to 2**96 fixed point.
        // We do this by multiplying by 2**96 / 10**18. But since
        // ln(x * C) = ln(x) + ln(C), we can simply do nothing here
        // and add ln(2**96 / 10**18) at the end.

        /// @solidity memory-safe-assembly
        assembly {
            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 := or(r, shl(3, lt(0xff, shr(r, x))))
            r := or(r, shl(2, lt(0xf, shr(r, x))))
            r := or(r, shl(1, lt(0x3, shr(r, x))))
            r := or(r, lt(0x1, shr(r, x)))
        }

        // Reduce range of x to (1, 2) * 2**96
        // ln(2^k * x) = k * ln(2) + ln(x)
        int256 k = r - 96;
        x <<= uint256(159 - k);
        x = int256(uint256(x) >> 159);

        // Evaluate using a (8, 8)-term rational approximation.
        // p is made monic, we will multiply by a scale factor later.
        int256 p = x + 3273285459638523848632254066296;
        p = ((p * x) >> 96) + 24828157081833163892658089445524;
        p = ((p * x) >> 96) + 43456485725739037958740375743393;
        p = ((p * x) >> 96) - 11111509109440967052023855526967;
        p = ((p * x) >> 96) - 45023709667254063763336534515857;
        p = ((p * x) >> 96) - 14706773417378608786704636184526;
        p = p * x - (795164235651350426258249787498 << 96);

        // We leave p in 2**192 basis so we don't need to scale it back up for the division.
        // q is monic by convention.
        int256 q = x + 5573035233440673466300451813936;
        q = ((q * x) >> 96) + 71694874799317883764090561454958;
        q = ((q * x) >> 96) + 283447036172924575727196451306956;
        q = ((q * x) >> 96) + 401686690394027663651624208769553;
        q = ((q * x) >> 96) + 204048457590392012362485061816622;
        q = ((q * x) >> 96) + 31853899698501571402653359427138;
        q = ((q * x) >> 96) + 909429971244387300277376558375;
        /// @solidity memory-safe-assembly
        assembly {
            // Div in assembly because solidity adds a zero check despite the unchecked.
            // The q polynomial is known not to have zeros in the domain.
            // No scaling required because p is already 2**96 too large.
            r := sdiv(p, q)
        }

        // r is in the range (0, 0.125) * 2**96

        // Finalization, we need to:
        // * multiply by the scale factor s = 5.549…
        // * add ln(2**96 / 10**18)
        // * add k * ln(2)
        // * multiply by 10**18 / 2**96 = 5**18 >> 78

        // mul s * 5e18 * 2**96, base is now 5**18 * 2**192
        r *= 1677202110996718588342820967067443963516166;
        // add ln(2) * k * 5e18 * 2**192
        r += 16597577552685614221487285958193947469193820559219878177908093499208371 * k;
        // add ln(2**96 / 10**18) * 5e18 * 2**192
        r += 600920179829731861736702779321621459595472258049074101567377883020018308;
        // base conversion: mul 2**18 / 2**192
        r >>= 174;
    }
}

/// @dev Will return 0 instead of reverting if y is zero.
function unsafeDiv(int256 x, int256 y) pure returns (int256 r) {
    /// @solidity memory-safe-assembly
    assembly {
        // Divide x by y.
        r := sdiv(x, y)
    }
}

File 22 of 27 : IERC20.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}

File 23 of 27 : IERC20Metadata.sol
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

File 24 of 27 : IERC721Receiver.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data)
        external
        returns (bytes4);
}

File 25 of 27 : IGoober.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import "./IERC20Metadata.sol";
import "./IERC721Receiver.sol";

interface IGoober is IERC721Receiver {
    // Errors

    // Balance Errors
    error InsufficientAllowance();
    error InsufficientGoo(uint256 amount, uint256 actualK, uint256 expectedK);

    // Deposit Errors
    error InsufficientLiquidityDeposited();
    error MintBelowLimit();

    // K Calculation Errors
    error MustLeaveLiquidity(uint256 gooBalance, uint256 gobblerBalance);

    // Mint Errors
    error AuctionPriceTooHigh(uint256 auctionPrice, uint256 poolPrice);
    error InsufficientLiquidity(uint256 gooBalance, uint256 gobblerBalance);
    error MintFailed();

    // NFT Errors
    error InvalidNFT();
    error InvalidMultiplier(uint256 gobblerId);

    // Skim Errors
    error NoSkim();

    // Swap Errors
    error InsufficientInputAmount(uint256 amount0In, uint256 amount1In);
    error InsufficientOutputAmount(uint256 gooOut, uint256 gobblersOut);
    error InvalidReceiver(address receiver);
    error ExcessiveErroneousGoo(uint256 actualErroneousGoo, uint256 allowedErroneousGoo);

    // Time Errors
    error Expired(uint256 time, uint256 deadline);

    // Withdraw Errors
    error InsufficientLiquidityWithdrawn();
    error BurnAboveLimit();

    /**
     * @notice The caller doesn't have permission to access the function.
     * @param accessor The requesting address.
     * @param permissioned The address which has the requisite permissions.
     */
    error AccessControlViolation(address accessor, address permissioned);

    /**
     * @notice Invalid feeTo address.
     * @param feeTo the feeTo address.
     */
    error InvalidAddress(address feeTo);

    // Structs

    /// @dev Intermediary struct for swap calculation.
    struct SwapData {
        uint256 gooReserve;
        uint256 gobblerReserve;
        uint256 gooBalance;
        uint256 gobblerBalance;
        uint256 multOut;
        uint256 amount0In;
        uint256 amount1In;
        int256 erroneousGoo;
    }

    // Events

    event VaultMint(address indexed minter, uint256 auctionPricePerMult, uint256 poolPricePerMult, uint256 gooConsumed);

    event Deposit(
        address indexed caller, address indexed receiver, uint256[] gobblers, uint256 gooTokens, uint256 fractions
    );

    event Withdraw(
        address indexed caller,
        address indexed receiver,
        address indexed owner,
        uint256[] gobblers,
        uint256 gooTokens,
        uint256 fractions
    );

    event FeesAccrued(address indexed feeTo, uint256 fractions, bool performanceFee, uint256 _deltaK);

    event Swap(
        address indexed caller,
        address indexed receiver,
        uint256 gooTokensIn,
        uint256 gobblersMultIn,
        uint256 gooTokensOut,
        uint256 gobblerMultOut
    );

    event Sync(uint256 gooBalance, uint256 multBalance);

    /*//////////////////////////////////////////////////////////////
    // External: Non Mutating
    //////////////////////////////////////////////////////////////*/

    /// @return gooTokens The total amount of Goo owned.
    /// @return gobblerMult The total multiple of all Gobblers owned.
    function totalAssets() external view returns (uint256 gooTokens, uint256 gobblerMult);

    /// @param gooTokens - The amount of Goo to simulate.
    /// @param gobblerMult - The amount of Gobbler mult in to simulate.
    /// @return fractions - The fractions, without any fees assessed, which would be returned for a deposit.
    function convertToFractions(uint256 gooTokens, uint256 gobblerMult) external view returns (uint256 fractions);

    /// @param fractions The amount of fractions to simulate converting.
    /// @param gooTokens - The amount of Goo out.
    /// @param gobblerMult - The amount of Gobbler mult out.
    function convertToAssets(uint256 fractions) external view returns (uint256 gooTokens, uint256 gobblerMult);

    /// @notice Gets the vault reserves of Goo and Gobbler mult, along with the last update time.
    /// @dev This can be used to calculate slippage on a swap of certain sizes
    /// @dev using Uni V2 style liquidity math.
    /// @return _gooReserve - The amount of Goo in the tank for the pool.
    /// @return _gobblerReserve - The total multiplier of all Gobblers in the pool.
    /// @return _blockTimestampLast - The last time that the oracles were updated.
    function getReserves()
        external
        view
        returns (uint256 _gooReserve, uint256 _gobblerReserve, uint32 _blockTimestampLast);

    /// @notice Previews a deposit of the supplied Gobblers and Goo.
    /// @param gobblers - Array of Gobbler ids.
    /// @param gooTokens - Amount of Goo to deposit.
    /// @return fractions - Amount of fractions created.
    function previewDeposit(uint256[] calldata gobblers, uint256 gooTokens) external view returns (uint256 fractions);

    /// @notice Previews a withdraw of the requested Gobblers and Goo tokens from the vault.
    /// @param gobblers - Array of Gobbler ids.
    /// @param gooTokens - Amount of Goo to withdraw.
    /// @return fractions - Amount of fractions withdrawn.
    function previewWithdraw(uint256[] calldata gobblers, uint256 gooTokens)
        external
        view
        returns (uint256 fractions);

    /// @notice Simulates a swap.
    /// @param gobblersIn - Array of Gobbler ids to swap in.
    /// @param gooIn - Amount of Goo to swap in.
    /// @param gobblersOut - Array of Gobbler ids to swap out.
    /// @param gooOut - Amount of Goo to swap out.
    /// @return erroneousGoo - The amount in wei by which to increase or decrease gooIn/Out to balance the swap.
    function previewSwap(uint256[] calldata gobblersIn, uint256 gooIn, uint256[] calldata gobblersOut, uint256 gooOut)
        external
        view
        returns (int256 erroneousGoo);

    /*//////////////////////////////////////////////////////////////
    // External: Mutating, Restricted Access
    //////////////////////////////////////////////////////////////*/

    // Access Control

    /**
     * @notice Updates the address that fees are sent to.
     * @param newFeeTo The new address to which fees will be sent.
     */
    function setFeeTo(address newFeeTo) external;

    /**
     * @notice Updates the address that can call mintGobbler.
     * @param newMinter The new address to which will be able to call mintGobbler.
     */
    function setMinter(address newMinter) external;

    // Other Privileged Functions

    /// @notice Mints Gobblers using the pool's virtual reserves of Goo
    /// @notice when specific conditions are met.
    function mintGobbler() external;

    /// @notice Restricted function for skimming any ERC20s that may have been erroneously sent to the pool.
    function skim(address erc20) external;

    /// @notice Restricted function for blocking/unblocking compromised Gobblers from the pool.
    function flagGobbler(uint256 tokenId, bool _flagged) external;

    /*//////////////////////////////////////////////////////////////
    // External: Mutating, Unrestricted
    //////////////////////////////////////////////////////////////*/

    /// @notice Deposits the supplied Gobblers/Goo from the owner and sends fractions to the receiver.
    /// @param gobblers - Array of Gobbler ids.
    /// @param gooTokens - Amount of Goo to deposit.
    /// @param receiver - Address to receive fractions.
    /// @return fractions - Amount of fractions created.
    function deposit(uint256[] calldata gobblers, uint256 gooTokens, address receiver)
        external
        returns (uint256 fractions);

    /// @notice Deposits the supplied Gobblers/Goo from the owner and sends fractions to the
    /// @notice receiver whilst ensuring a deadline is met, and a minimum amount of fractions are created.
    /// @param gobblers - Array of Gobbler ids to deposit.
    /// @param gooTokens - Amount of Goo to deposit.
    /// @param receiver - Address to receive fractions.
    /// @param minFractionsOut - Minimum amount of fractions to be sent.
    /// @param deadline - Unix timestamp by which the transaction must execute.
    /// @return fractions - Amount of fractions created.
    function safeDeposit(
        uint256[] calldata gobblers,
        uint256 gooTokens,
        address receiver,
        uint256 minFractionsOut,
        uint256 deadline
    ) external returns (uint256 fractions);

    /// @notice Withdraws the requested Gobblers and Goo from the vault.
    /// @param gobblers - Array of Gobbler ids to withdraw
    /// @param gooTokens - Amount of Goo to withdraw.
    /// @param receiver - Address to receive the Goo and Gobblers.
    /// @param owner - Owner of the fractions to be destroyed.
    /// @return fractions - Amount of fractions destroyed.
    function withdraw(uint256[] calldata gobblers, uint256 gooTokens, address receiver, address owner)
        external
        returns (uint256 fractions);

    /// @notice Withdraws the requested Gobblers/Goo from the vault to the receiver and destroys fractions
    /// @notice from the owner whilst ensuring a deadline is met, and a maximimum amount of fractions are destroyed.
    /// @param gobblers - Array of Gobbler ids to withdraw.
    /// @param gooTokens - Amount of Goo to withdraw.
    /// @param receiver - Address to receive the Goo and Gobblers.
    /// @param owner - Owner of the fractions to be destroyed.
    /// @param maxFractionsIn - Maximum amount of fractions to be destroyed.
    /// @param deadline - Unix timestamp by which the transaction must execute.
    /// @return fractions - Aamount of fractions destroyed.
    function safeWithdraw(
        uint256[] calldata gobblers,
        uint256 gooTokens,
        address receiver,
        address owner,
        uint256 maxFractionsIn,
        uint256 deadline
    ) external returns (uint256 fractions);

    /// @notice Swaps supplied Gobblers/Goo for Gobblers/Goo in the pool.
    function swap(
        uint256[] calldata gobblersIn,
        uint256 gooIn,
        uint256[] calldata gobblersOut,
        uint256 gooOut,
        address receiver,
        bytes calldata data
    ) external returns (int256 erroneousGoo);

    /// @notice Swaps supplied Gobblers/Goo for Gobblers/Goo in the pool, with slippage and deadline control.
    function safeSwap(
        uint256 erroneousGooAbs,
        uint256 deadline,
        uint256[] calldata gobblersIn,
        uint256 gooIn,
        uint256[] calldata gobblersOut,
        uint256 gooOut,
        address receiver,
        bytes calldata data
    ) external returns (int256 erroneousGoo);
}

File 26 of 27 : IGooberCallee.sol
// SPDX-License-Identifier: MIT

pragma solidity >=0.8.0;

import "./IGoober.sol";

interface IGooberCallee {
    function gooberCall(bytes calldata data) external;
}

File 27 of 27 : UQ112x112.sol
// SPDX-License-Identifier: GPL

pragma solidity ^0.8.17;

// a library for handling binary fixed point numbers (https://en.wikipedia.org/wiki/Q_(number_format))

// range: [0, 2**112 - 1]
// resolution: 1 / 2**112

library UQ112x112 {
    uint224 public constant Q112 = 2 ** 112;

    // encode a uint112 as a UQ112x112
    function encode(uint112 y) internal pure returns (uint224 z) {
        z = uint224(y) * Q112; // never overflows
    }

    // divide a UQ112x112 by a uint112, returning a UQ112x112
    function uqdiv(uint224 x, uint112 y) internal pure returns (uint224 z) {
        z = x / uint224(y);
    }
}

Settings
{
  "remappings": [
    "VRGDAs/=lib/art-gobblers/lib/VRGDAs/src/",
    "art-gobblers/=lib/art-gobblers/src/",
    "chainlink/=lib/art-gobblers/lib/chainlink/contracts/src/",
    "ds-test/=lib/forge-std/lib/ds-test/src/",
    "forge-std/=lib/forge-std/src/",
    "goo-issuance/=lib/art-gobblers/lib/goo-issuance/src/",
    "solmate/=lib/solmate/src/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 2000000
  },
  "metadata": {
    "bytecodeHash": "ipfs"
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "london",
  "libraries": {}
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_gobblersAddress","type":"address"},{"internalType":"address","name":"_gooAddress","type":"address"},{"internalType":"address","name":"_feeTo","type":"address"},{"internalType":"address","name":"_minter","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"accessor","type":"address"},{"internalType":"address","name":"permissioned","type":"address"}],"name":"AccessControlViolation","type":"error"},{"inputs":[{"internalType":"uint256","name":"auctionPrice","type":"uint256"},{"internalType":"uint256","name":"poolPrice","type":"uint256"}],"name":"AuctionPriceTooHigh","type":"error"},{"inputs":[],"name":"BurnAboveLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualErroneousGoo","type":"uint256"},{"internalType":"uint256","name":"allowedErroneousGoo","type":"uint256"}],"name":"ExcessiveErroneousGoo","type":"error"},{"inputs":[{"internalType":"uint256","name":"time","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"Expired","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"actualK","type":"uint256"},{"internalType":"uint256","name":"expectedK","type":"uint256"}],"name":"InsufficientGoo","type":"error"},{"inputs":[{"internalType":"uint256","name":"amount0In","type":"uint256"},{"internalType":"uint256","name":"amount1In","type":"uint256"}],"name":"InsufficientInputAmount","type":"error"},{"inputs":[{"internalType":"uint256","name":"gooBalance","type":"uint256"},{"internalType":"uint256","name":"gobblerBalance","type":"uint256"}],"name":"InsufficientLiquidity","type":"error"},{"inputs":[],"name":"InsufficientLiquidityDeposited","type":"error"},{"inputs":[],"name":"InsufficientLiquidityWithdrawn","type":"error"},{"inputs":[{"internalType":"uint256","name":"gooOut","type":"uint256"},{"internalType":"uint256","name":"gobblersOut","type":"uint256"}],"name":"InsufficientOutputAmount","type":"error"},{"inputs":[{"internalType":"address","name":"feeTo","type":"address"}],"name":"InvalidAddress","type":"error"},{"inputs":[{"internalType":"uint256","name":"gobblerId","type":"uint256"}],"name":"InvalidMultiplier","type":"error"},{"inputs":[],"name":"InvalidNFT","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"InvalidReceiver","type":"error"},{"inputs":[],"name":"MintBelowLimit","type":"error"},{"inputs":[],"name":"MintFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"gooBalance","type":"uint256"},{"internalType":"uint256","name":"gobblerBalance","type":"uint256"}],"name":"MustLeaveLiquidity","type":"error"},{"inputs":[],"name":"NoSkim","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"gooTokens","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fractions","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"feeTo","type":"address"},{"indexed":false,"internalType":"uint256","name":"fractions","type":"uint256"},{"indexed":false,"internalType":"bool","name":"performanceFee","type":"bool"},{"indexed":false,"internalType":"uint256","name":"_deltaK","type":"uint256"}],"name":"FeesAccrued","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"gooTokensIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gobblersMultIn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gooTokensOut","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gobblerMultOut","type":"uint256"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"gooBalance","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"multBalance","type":"uint256"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"uint256","name":"auctionPricePerMult","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"poolPricePerMult","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"gooConsumed","type":"uint256"}],"name":"VaultMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"gooTokens","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"fractions","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGEMENT_FEE_BPS","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERFORMANCE_FEE_BPS","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"artGobblers","outputs":[{"internalType":"contract ArtGobblers","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockTimestampLast","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"name":"convertToAssets","outputs":[{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"uint256","name":"gobblerMult","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"uint256","name":"gobblerMult","type":"uint256"}],"name":"convertToFractions","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feeTo","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bool","name":"_flagged","type":"bool"}],"name":"flagGobbler","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"flagged","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint256","name":"_gooReserve","type":"uint256"},{"internalType":"uint256","name":"_gobblerReserve","type":"uint256"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"goo","outputs":[{"internalType":"contract Goo","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kDebt","outputs":[{"internalType":"uint112","name":"","type":"uint112"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"kLast","outputs":[{"internalType":"uint112","name":"","type":"uint112"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintGobbler","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"minter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"internalType":"uint256","name":"gooTokens","type":"uint256"}],"name":"previewDeposit","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblersIn","type":"uint256[]"},{"internalType":"uint256","name":"gooIn","type":"uint256"},{"internalType":"uint256[]","name":"gobblersOut","type":"uint256[]"},{"internalType":"uint256","name":"gooOut","type":"uint256"}],"name":"previewSwap","outputs":[{"internalType":"int256","name":"erroneousGoo","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"internalType":"uint256","name":"gooTokens","type":"uint256"}],"name":"previewWithdraw","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceGobblerCumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"priceGooCumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"minFractionsOut","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"safeDeposit","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"erroneousGooAbs","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256[]","name":"gobblersIn","type":"uint256[]"},{"internalType":"uint256","name":"gooIn","type":"uint256"},{"internalType":"uint256[]","name":"gobblersOut","type":"uint256[]"},{"internalType":"uint256","name":"gooOut","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"safeSwap","outputs":[{"internalType":"int256","name":"erroneousGoo","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"maxFractionsIn","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"safeWithdraw","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFeeTo","type":"address"}],"name":"setFeeTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newMinter","type":"address"}],"name":"setMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"erc20","type":"address"}],"name":"skim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblersIn","type":"uint256[]"},{"internalType":"uint256","name":"gooIn","type":"uint256"},{"internalType":"uint256[]","name":"gobblersOut","type":"uint256[]"},{"internalType":"uint256","name":"gooOut","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalAssets","outputs":[{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"uint256","name":"gobblerMult","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"gobblers","type":"uint256[]"},{"internalType":"uint256","name":"gooTokens","type":"uint256"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"withdraw","outputs":[{"internalType":"uint256","name":"fractions","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}]

61012060405260016000553480156200001757600080fd5b50604051620058ce380380620058ce8339810160408190526200003a91620001ad565b6040518060400160405280600681526020016523b7b7b132b960d11b8152506040518060400160405280600381526020016223a12960e91b81525060128260019081620000889190620002af565b506002620000978382620002af565b5060ff81166080524660a052620000ad620000f4565b60c0525050600780546001600160a01b03199081166001600160a01b0395861617909155600880549091169284169290921790915550918216610100521660e052620003f9565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60016040516200012891906200037b565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b80516001600160a01b0381168114620001a857600080fd5b919050565b60008060008060808587031215620001c457600080fd5b620001cf8562000190565b9350620001df6020860162000190565b9250620001ef6040860162000190565b9150620001ff6060860162000190565b905092959194509250565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806200023557607f821691505b6020821081036200025657634e487b7160e01b600052602260045260246000fd5b50919050565b601f821115620002aa57600081815260208120601f850160051c81016020861015620002855750805b601f850160051c820191505b81811015620002a65782815560010162000291565b5050505b505050565b81516001600160401b03811115620002cb57620002cb6200020a565b620002e381620002dc845462000220565b846200025c565b602080601f8311600181146200031b5760008415620003025750858301515b600019600386901b1c1916600185901b178555620002a6565b600085815260208120601f198616915b828110156200034c578886015182559484019460019091019084016200032b565b50858210156200036b5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60008083546200038b8162000220565b60018281168015620003a65760018114620003bc57620003ed565b60ff1984168752821515830287019450620003ed565b8760005260208060002060005b85811015620003e45781548a820152908401908201620003c9565b50505082870194505b50929695505050505050565b60805160a05160c05160e051610100516153a96200052560003960008181610453015281816107de0152818161087a01528181610a1401528181610ab001528181610cca01528181610da001528181610e5401528181610f5a0152818161102f0152818161110c015281816111e2015281816114b5015281816115b601528181611705015281816118c4015281816119c501528181611e2201528181611f4a015281816120130152818161210e015281816122f5015281816123740152818161264001528181612aa001528181612b1f01528181613209015281816132d801526133a90152600081816104a001528181611dcd01528181611fd50152818161229e01528181612a490152613294015260006118500152600061181b015260006104da01526153a96000f3fe608060405234801561001057600080fd5b50600436106102e95760003560e01c806370a0823111610191578063bc25cf77116100e3578063d99f958f11610097578063ec5357c611610071578063ec5357c61461075d578063f46901ed14610770578063fca3b5aa1461078357600080fd5b8063d99f958f1461070c578063dd62ed3e1461071f578063df4209ed1461074a57600080fd5b8063c24317ff116100c8578063c24317ff146106a1578063c5700a02146106b4578063d505accf146106f957600080fd5b8063bc25cf7714610686578063bcf90d2b1461069957600080fd5b806395d89b4111610145578063a752d9aa1161011f578063a752d9aa14610657578063a9059cbb14610660578063b590bc621461067357600080fd5b806395d89b41146106235780639a31e7e31461062b578063a370c6681461063457600080fd5b80637ecebe00116101765780637ecebe00146105c4578063837c7dc2146105e4578063872b9a9a146105f757600080fd5b806370a082311461056b5780637464fc3d1461058b57600080fd5b80631d1c9e591161024a578063313ce567116101fe578063529fb8c1116101d8578063529fb8c1146105295780635922e56f1461053c5780635b01e3741461054f57600080fd5b8063313ce567146104d55780633644e5151461050e57806340874f011461051657600080fd5b806323b872dd1161022f57806323b872dd14610488578063274cdd5c1461049b5780632a01e6f9146104c257600080fd5b80631d1c9e591461044e57806320bb4adf1461047557600080fd5b80630902f1ac116102a157806309c0478c1161028657806309c0478c146103e9578063150b7a02146103f357806318160ddd1461043757600080fd5b80630902f1ac1461039d578063095ea7b3146103c657600080fd5b806306fdde03116102d257806306fdde0314610355578063075461721461036a57806307a2d13a1461038a57600080fd5b8063017e7e58146102ee57806301e1d11414610338575b600080fd5b60075461030e9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b610340610796565b6040805192835260208301919091520161032f565b61035d610900565b60405161032f919061477a565b60085461030e9073ffffffffffffffffffffffffffffffffffffffff1681565b6103406103983660046147e6565b61098e565b6103a56109ca565b60408051938452602084019290925263ffffffff169082015260600161032f565b6103d96103d4366004614834565b610b63565b604051901515815260200161032f565b6103f1610bdd565b005b6104066104013660046148a9565b6110f2565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200161032f565b61044060035481565b60405190815260200161032f565b61030e7f000000000000000000000000000000000000000000000000000000000000000081565b61044061048336600461491c565b6112ce565b6103d961049636600461493e565b61133f565b61030e7f000000000000000000000000000000000000000000000000000000000000000081565b6104406104d03660046149c4565b611483565b6104fc7f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff909116815260200161032f565b610440611817565b610440610524366004614a46565b611872565b610440610537366004614a92565b611b44565b61044061054a366004614b6a565b611c73565b6105586103e881565b60405161ffff909116815260200161032f565b610440610579366004614bdb565b60046020526000908152604090205481565b600b546105a5906dffffffffffffffffffffffffffff1681565b6040516dffffffffffffffffffffffffffff909116815260200161032f565b6104406105d2366004614bdb565b60066020526000908152604090205481565b6104406105f2366004614bf8565b611d0c565b600b546105a5906e01000000000000000000000000000090046dffffffffffffffffffffffffffff1681565b61035d61255e565b61044060095481565b6103d96106423660046147e6565b600c6020526000908152604090205460ff1681565b610440600a5481565b6103d961066e366004614834565b61256b565b610440610681366004614a46565b6125f0565b6103f1610694366004614bdb565b6127c1565b61055860c881565b6104406106af366004614cbc565b61299c565b600b546106e4907c0100000000000000000000000000000000000000000000000000000000900463ffffffff1681565b60405163ffffffff909116815260200161032f565b6103f1610707366004614d1b565b612d74565b6103f161071a366004614d92565b613093565b61044061072d366004614dc7565b600560209081526000928352604080842090915290825290205481565b610440610758366004614df5565b613144565b61044061076b366004614e69565b6136cc565b6103f161077e366004614bdb565b613767565b6103f1610791366004614bdb565b613886565b6040517fd075fbba000000000000000000000000000000000000000000000000000000008152306004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063d075fbba90602401602060405180830381865afa158015610825573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108499190614eef565b6040517f11f93e780000000000000000000000000000000000000000000000000000000081523060048201529092507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906311f93e7890602401602060405180830381865afa1580156108d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108fa9190614eef565b90509091565b6001805461090d90614f08565b80601f016020809104026020016040519081016040528092919081815260200182805461093990614f08565b80156109865780601f1061095b57610100808354040283529160200191610986565b820191906000526020600020905b81548152906001019060200180831161096957829003601f168201915b505050505081565b600354600090819080156109c4576109a4610796565b90935091506109b48484836139a5565b92506109c18483836139a5565b91505b50915091565b6040517fd075fbba0000000000000000000000000000000000000000000000000000000081523060048201526000908190819073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063d075fbba90602401602060405180830381865afa158015610a5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7f9190614eef565b6040517f11f93e780000000000000000000000000000000000000000000000000000000081523060048201529093507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16906311f93e7890602401602060405180830381865afa158015610b0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b309190614eef565b600b54939490937c0100000000000000000000000000000000000000000000000000000000900463ffffffff1692509050565b33600081815260056020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590610bcb9086815260200190565b60405180910390a35060015b92915050565b600054600114610c4e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b600260005560085473ffffffffffffffffffffffffffffffffffffffff163314610cc6576008546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166310f255f56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d579190614eef565b6040517fd075fbba00000000000000000000000000000000000000000000000000000000815230600482015290915060009073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169063d075fbba90602401602060405180830381865afa158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b9190614eef565b6040517f11f93e7800000000000000000000000000000000000000000000000000000000815230600482015290915060009073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016906311f93e7890602401602060405180830381865afa158015610e9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ebf9190614eef565b90508160008080610ed18486896139e1565b91945092509050821515600003610f1e576040517fde8591610000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610c45565b82156110d5576040517fc9bddac600000000000000000000000000000000000000000000000000000000815260048101889052600160248201527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063c9bddac6906044016020604051808303816000875af1158015610fb8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fdc9190614eef565b50610fe78785614f8a565b604080518481526020810184905290810189905290945033907ff79584ad48f1b6f4760ff2c645a0bc47324f626fb535ad3aa1a4f7e49525651a9060600160405180910390a27f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166310f255f56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611098573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110bc9190614eef565b96506110c98486896139e1565b91945092509050610f1e565b6110e484868888600180613a81565b505060016000555050505050565b60003373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001614611163576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000848152600c602052604090205460ff1615156001036111b0576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ffa522a15000000000000000000000000000000000000000000000000000000008152600481018590526000907f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff169063fa522a1590602401602060405180830381865afa15801561123e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112629190614eef565b905060068110156112a2576040517f9f44c9eb00000000000000000000000000000000000000000000000000000000815260048101869052602401610c45565b507f150b7a02000000000000000000000000000000000000000000000000000000009695505050505050565b600354600090816112e76112e28587614f9d565b613d18565b90508115611333576000806112fa610796565b9092509050600061130e6112e28385614f9d565b9050600061131c8583613dca565b90506113288682613de6565b965050505050611337565b8092505b505092915050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526005602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146113d3576113a18382614f8a565b73ffffffffffffffffffffffffffffffffffffffff861660009081526005602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff851660009081526004602052604081208054859290611408908490614f8a565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260046020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906114709087815260200190565b60405180910390a3506001949350505050565b60008060006114906109ca565b50909250905060006114a28584614f8a565b905081600080805b898110156116eb57307f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16636352211e8d8d8581811061150157611501614fb4565b905060200201356040518263ffffffff1660e01b815260040161152691815260200190565b602060405180830381865afa158015611543573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115679190614fe3565b73ffffffffffffffffffffffffffffffffffffffff16146115b4576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fa522a158c8c8481811061160257611602614fb4565b905060200201356040518263ffffffff1660e01b815260040161162791815260200190565b602060405180830381865afa158015611644573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116689190614eef565b915060068210156116c1578a8a8281811061168557611685614fb4565b905060200201356040517f9f44c9eb000000000000000000000000000000000000000000000000000000008152600401610c4591815260200190565b6116cb8285614f8a565b93506116d78284615000565b9250806116e381615013565b9150506114aa565b506116f68b85615000565b935060005b8c8110156117f2577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fa522a158f8f8481811061175157611751614fb4565b905060200201356040518263ffffffff1660e01b815260040161177691815260200190565b602060405180830381865afa158015611793573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117b79190614eef565b915060068210156117d4578d8d8281811061168557611685614fb4565b6117de8285615000565b9350806117ea81615013565b9150506116fb565b50611803868686868c876000613dfb565b50909e9d5050505050505050505050505050565b60007f0000000000000000000000000000000000000000000000000000000000000000461461184d57611848614001565b905090565b507f000000000000000000000000000000000000000000000000000000000000000090565b600080600061187f6109ca565b5060035491935091506000611894848461409b565b5050905080826118a49190615000565b915060006118b28786614f8a565b9050836000805b8a811015611ab257307f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff16636352211e8e8e8581811061191057611910614fb4565b905060200201356040518263ffffffff1660e01b815260040161193591815260200190565b602060405180830381865afa158015611952573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119769190614fe3565b73ffffffffffffffffffffffffffffffffffffffff16146119c3576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fa522a158d8d84818110611a1157611a11614fb4565b905060200201356040518263ffffffff1660e01b8152600401611a3691815260200190565b602060405180830381865afa158015611a53573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a779190614eef565b91506006821015611a94578b8b8281811061168557611685614fb4565b611a9e8284614f8a565b925080611aaa81615013565b9150506118b9565b506000611abf8388614f8a565b90506000811180611ad0575060008a115b611b06576040517ff94cc92a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611b238585611b1a6112e28c8e614f9d565b60006001614180565b509350505050611b338782614285565b9d9c50505050505050505050505050565b60008a80421115611b8a576040517faa2fd92500000000000000000000000000000000000000000000000000000000815242600482015260248101829052604401610c45565b611b988b8b8b8b8b8b611483565b91506000821215611c06576000611bae8361504b565b90508d811115611bf4576040517fd1d03be700000000000000000000000000000000000000000000000000000000815260048101829052602481018f9052604401610c45565b611bfe8188615000565b965050611c62565b6000821315611c6257818d811115611c54576040517fd1d03be700000000000000000000000000000000000000000000000000000000815260048101829052602481018f9052604401610c45565b611c5e818b615000565b9950505b611b338b8b8b8b8b8b8b8b8b611d0c565b60008180421115611cb9576040517faa2fd92500000000000000000000000000000000000000000000000000000000815242600482015260248101829052604401610c45565b611cc58888888861299c565b915083821015611d01576040517f40ff566600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b509695505050505050565b60008054600114611d79576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b600260005584151580611d8b57508515155b611dcb576040517fd28d3eb50000000000000000000000000000000000000000000000000000000081526004810186905260248101879052604401610c45565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480611e7057507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16145b15611ebf576040517f9cfea58300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602401610c45565b60006040518061010001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152509050611f0d6109ca565b50602083015281528515611ffe576040517f9cc397fb000000000000000000000000000000000000000000000000000000008152600481018790527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690639cc397fb90602401600060405180830381600087803b158015611fa357600080fd5b505af1158015611fb7573d6000803e3d6000fd5b50611ffe92505073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169050868861429a565b86156121f15760005b878110156121ef5760007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fa522a158b8b8581811061205f5761205f614fb4565b905060200201356040518263ffffffff1660e01b815260040161208491815260200190565b602060405180830381865afa1580156120a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120c59190614eef565b905060068110156120e25789898381811061168557611685614fb4565b80836080018181516120f49190615000565b90525073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000166323b872dd30898d8d8781811061214757612147614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b1580156121c357600080fd5b505af11580156121d7573d6000803e3d6000fd5b505050505080806121e790615013565b915050612007565b505b821561227e576040517fe53e56d500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff86169063e53e56d59061224b9087908790600401615083565b600060405180830381600087803b15801561226557600080fd5b505af1158015612279573d6000803e3d6000fd5b505050505b8815612367576122c673ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000000000000000000000000000000000000000000001633308c614359565b6040517f0b38049f000000000000000000000000000000000000000000000000000000008152600481018a90527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690630b38049f90602401600060405180830381600087803b15801561234e57600080fd5b505af1158015612362573d6000803e3d6000fd5b505050505b60005b8a811015612469577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166342842e0e33308f8f868181106123c2576123c2614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b15801561243e57600080fd5b505af1158015612452573d6000803e3d6000fd5b50505050808061246190615013565b91505061236a565b506124726109ca565b5060608301819052604083018290528251602084015160808501516124a0949293919291908b906001613dfb565b60c084015260a083015260e082015260408101516060820151825160208401516124cf93929190600080613a81565b60a081015160c0820151608083015160405173ffffffffffffffffffffffffffffffffffffffff89169333937fb3e2773606abfd36b5bd91394b3a54d1398336c65005baf7bf7a05efeffaf75b9361253f938d919093845260208401929092526040830152606082015260800190565b60405180910390a360e0015160016000559a9950505050505050505050565b6002805461090d90614f08565b3360009081526004602052604081208054839190839061258c908490614f8a565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260046020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610bcb9086815260200190565b60008060006125fd6109ca565b5091509150600061260e838361409b565b505090506000600354826126229190615000565b905060006126308588615000565b90508360005b89811015612710577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fa522a158c8c8481811061268c5761268c614fb4565b905060200201356040518263ffffffff1660e01b81526004016126b191815260200190565b602060405180830381865afa1580156126ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126f29190614eef565b6126fc9083615000565b91508061270881615013565b915050612636565b506000806127268484611b1a6112e28b8d614f9d565b5093505050915084600003612757576103e861274683633b9aca00614f9d565b6127509190614f8a565b9850612764565b6127618582613de6565b98505b8860000361279e576040517fd04ebef000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6127a78961441f565b6127b1908a614f8a565b9c9b505050505050505050505050565b60005460011461282d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b600260005560075473ffffffffffffffffffffffffffffffffffffffff1633146128a5576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa158015612912573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129369190614eef565b905080600003612972576040517f368a711600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61299373ffffffffffffffffffffffffffffffffffffffff8316338361429a565b50506001600055565b60008054600114612a09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b6002600090815580612a196109ca565b5091509150612a288282614439565b508415612b1257612a7173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000000000000000000000000000000000000000000016333088614359565b6040517f0b38049f000000000000000000000000000000000000000000000000000000008152600481018690527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690630b38049f90602401600060405180830381600087803b158015612af957600080fd5b505af1158015612b0d573d6000803e3d6000fd5b505050505b60005b86811015612c14577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166342842e0e33308b8b86818110612b6d57612b6d614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b158015612be957600080fd5b505af1158015612bfd573d6000803e3d6000fd5b505050508080612c0c90615013565b915050612b15565b50600080612c206109ca565b5060035491935091506000612c386112e28688614f9d565b9050600080612c4c86868560006001614180565b5093505050915083600003612c7d576103e8612c6c83633b9aca00614f9d565b612c769190614f8a565b9850612c8a565b612c878482613de6565b98505b88600003612cc4576040517fd04ebef000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050612cd185614545565b612cdb9086614f8a565b9450612ce786866145d7565b612cf78282868660006001613a81565b8573ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f06caae2b2e26a58cd4700e53b62553aa8689e11ecf1506dbc680a1cc615306c38b8b8b8a604051612d5a94939291906150d0565b60405180910390a350506001600055509095945050505050565b42841015612dde576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152606401610c45565b60006001612dea611817565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526006602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015612f3c573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590612fb757508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b61301d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152606401610c45565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526005602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60075473ffffffffffffffffffffffffffffffffffffffff163314613106576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b6000918252600c602052604090912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055565b600080546001146131b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b60026000908155806131c16109ca565b50909250905081816131d38282614439565b5087156132ca576040517f9cc397fb000000000000000000000000000000000000000000000000000000008152600481018990527f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1690639cc397fb90602401600060405180830381600087803b15801561326257600080fd5b505af1158015613276573d6000803e3d6000fd5b506132bd92505073ffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000169050888a61429a565b6132c78883614f8a565b91505b6000805b8a8110156134ab577f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663fa522a158d8d8481811061332457613324614fb4565b905060200201356040518263ffffffff1660e01b815260040161334991815260200190565b602060405180830381865afa158015613366573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061338a9190614eef565b915060068210156133a7578b8b8281811061168557611685614fb4565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166323b872dd308b8f8f868181106133f7576133f7614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b15801561347357600080fd5b505af1158015613487573d6000803e3d6000fd5b5050505081836134979190614f8a565b9250806134a381615013565b9150506132ce565b5060006134b88386614f8a565b905060008111806134c9575060008a115b6134ff576040517ff94cc92a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006135138585611b1a6112e28a8c614f9d565b5093505050506000600354905061352a8183614285565b9850505073ffffffffffffffffffffffffffffffffffffffff8816331461361b5773ffffffffffffffffffffffffffffffffffffffff88166000908152600560209081526040808320338452909152902054878110156135b6576040517f13be252b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114613619576135e78882614f8a565b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526005602090815260408083203384529091529020555b505b6136258888614650565b6136358484888860006001613a81565b8773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f1219fadb6b0ead011622d9ff05712029b1ba5ae340a49f25c9b5607a94a842c48f8f8f8d6040516136af94939291906150d0565b60405180910390a450506001600055509298975050505050505050565b60008180421115613712576040517faa2fd92500000000000000000000000000000000000000000000000000000000815242600482015260248101829052604401610c45565b61371f8989898989613144565b91508382111561375b576040517f6ca104df00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50979650505050505050565b60075473ffffffffffffffffffffffffffffffffffffffff1633146137da576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b73ffffffffffffffffffffffffffffffffffffffff811661383f576040517f8e4c8aa600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c45565b600780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60075473ffffffffffffffffffffffffffffffffffffffff1633146138f9576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b73ffffffffffffffffffffffffffffffffffffffff811661395e576040517f8e4c8aa600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c45565b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04841183021582026139da57600080fd5b5091020490565b600080808515806139f0575084155b15613a31576040517fa17e11d50000000000000000000000000000000000000000000000000000000081526004810187905260248101869052604401610c45565b83600003613a425760019250613a78565b83861115613a785762011e4e613a5a61271086614f9d565b613a64919061515e565b9150613a70858761515e565b905081811192505b93509350939050565b6000613a9264010000000042615172565b600b5490915063ffffffff7c0100000000000000000000000000000000000000000000000000000000909104811682039086908690831615801590613ad657508715155b8015613ae157508615155b15613b8b578263ffffffff16613b1e82613afa856146de565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690614709565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8316613b5e83613afa846146de565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600b80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff8716021790558480613bdf5750855b15613cd3576000613bf36112e28b8d614f9d565b600b549091506dffffffffffffffffffffffffffff90811690821681118015613c195750875b15613c8f57613c288282615186565b600b8054600e90613c5c9084906e01000000000000000000000000000090046dffffffffffffffffffffffffffff166151b4565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff1602179055505b8615613cd057600b80547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff84161790555b50505b604080518b8152602081018b90527fcf2aa50876cdfbb541206f89af0ee78d44a2abf8d328e37fa4917f982149848a910160405180910390a150505050505050505050565b60b581710100000000000000000000000000000000008110613d3f5760409190911b9060801c5b69010000000000000000008110613d5b5760209190911b9060401c5b650100000000008110613d735760109190911b9060201c5b63010000008110613d895760089190911b9060101c5b62010000010260121c80820401600190811c80830401811c80830401811c80830401811c80830401811c80830401811c80830401901c908190048111900390565b6000613ddf83670de0b6b3a7640000846139a5565b9392505050565b6000613ddf8383670de0b6b3a76400006139a5565b60008080613e09868b614f8a565b8811613e16576000613e2a565b613e20868b614f8a565b613e2a9089614f8a565b9150613e36858a614f8a565b8711613e43576000613e57565b613e4d858a614f8a565b613e579088614f8a565b90506000821180613e685750600081115b613ea8576040517fec3e79fb0000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610c45565b6000613eb5836003614f9d565b613ec18a6103e8614f9d565b613ecb9190614f8a565b90506000613eda836003614f9d565b613ee68a6103e8614f9d565b613ef09190614f8a565b90506000613efe8284614f9d565b90506000613f0c8d8f614f9d565b613f1990620f4240614f9d565b905080821015613faf576000613f52613f3986613f40613f398689614725565b6001614285565b613f4a9190614f8a565b6103e5614725565b90508815613f9d576040517ffa251a69000000000000000000000000000000000000000000000000000000008152600481018290526024810184905260448101839052606401610c45565b613fa781896151db565b975050613ff0565b80821115613ff057613fe3613fdc613fca613f398487614725565b613fd49087614f8a565b6103e8613dca565b6001613de6565b613fed90886151fb565b96505b505050509750975097945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6001604051614033919061521b565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600b54600090819081906dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004168115614177576000806000806141058b8b886dffffffffffffffffffffffffffff16886dffffffffffffffffffffffffffff166000614180565b94509450945094505060008411801561411b5750825b1561417257600354612710906103e8906141359085613de6565b61413f9190614f9d565b614149919061515e565b9850816dffffffffffffffffffffffffffff169650806dffffffffffffffffffffffffffff1697505b505050505b50509250925092565b6000808080806141936112e28a8c614f9d565b9450846000036141d9576040517f83cb13eb000000000000000000000000000000000000000000000000000000008152600481018b9052602481018a9052604401610c45565b505050848211600080826141f6576141f18589614f8a565b614200565b6142008886614f8a565b9350871561426a57821561424957861561424957868411614230576142258482615000565b905060009350614249565b61423a8782615000565b90506142468785614f8a565b93505b8515614260576142598489614725565b9150614278565b6142598489613dca565b614275600180614725565b91505b9550955095509550959050565b6000613ddf8383670de0b6b3a7640000614736565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d1160016000511416171691505080614353576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610c45565b50505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d1160016000511416171691505080614418576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610c45565b5050505050565b600061271061442f60c884614f9d565b610bd7919061515e565b600080600080614449868661409b565b9194509250905081156144b45781600b600e8282829054906101000a90046dffffffffffffffffffffffffffff166144819190615186565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff1602179055505b821561453b576007546144dd9073ffffffffffffffffffffffffffffffffffffffff16846145d7565b600754604080518581526001602082015290810183905273ffffffffffffffffffffffffffffffffffffffff909116907f5623b32a5de3b9c9ab0cc28fb483268639c2a9b4fef0da1c5a7cdff50da7e4859060600160405180910390a25b5090949350505050565b60006145508261441f565b6007549091506145769073ffffffffffffffffffffffffffffffffffffffff16826145d7565b6007546040805183815260006020820181905281830152905173ffffffffffffffffffffffffffffffffffffffff909216917f5623b32a5de3b9c9ab0cc28fb483268639c2a9b4fef0da1c5a7cdff50da7e4859181900360600190a2919050565b80600360008282546145e99190615000565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600460209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526004602052604081208054839290614685908490614f8a565b909155505060038054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001614644565b6000610bd76e0100000000000000000000000000006dffffffffffffffffffffffffffff84166152f1565b6000613ddf6dffffffffffffffffffffffffffff831684615338565b6000613ddf83670de0b6b3a7640000845b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048411830215820261476b57600080fd5b50910281810615159190040190565b600060208083528351808285015260005b818110156147a75785810183015185820160400152820161478b565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b6000602082840312156147f857600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461482157600080fd5b50565b803561482f816147ff565b919050565b6000806040838503121561484757600080fd5b8235614852816147ff565b946020939093013593505050565b60008083601f84011261487257600080fd5b50813567ffffffffffffffff81111561488a57600080fd5b6020830191508360208285010111156148a257600080fd5b9250929050565b6000806000806000608086880312156148c157600080fd5b85356148cc816147ff565b945060208601356148dc816147ff565b935060408601359250606086013567ffffffffffffffff8111156148ff57600080fd5b61490b88828901614860565b969995985093965092949392505050565b6000806040838503121561492f57600080fd5b50508035926020909101359150565b60008060006060848603121561495357600080fd5b833561495e816147ff565b9250602084013561496e816147ff565b929592945050506040919091013590565b60008083601f84011261499157600080fd5b50813567ffffffffffffffff8111156149a957600080fd5b6020830191508360208260051b85010111156148a257600080fd5b600080600080600080608087890312156149dd57600080fd5b863567ffffffffffffffff808211156149f557600080fd5b614a018a838b0161497f565b9098509650602089013595506040890135915080821115614a2157600080fd5b50614a2e89828a0161497f565b979a9699509497949695606090950135949350505050565b600080600060408486031215614a5b57600080fd5b833567ffffffffffffffff811115614a7257600080fd5b614a7e8682870161497f565b909790965060209590950135949350505050565b60008060008060008060008060008060006101008c8e031215614ab457600080fd5b8b359a5060208c0135995067ffffffffffffffff8060408e01351115614ad957600080fd5b614ae98e60408f01358f0161497f565b909a50985060608d0135975060808d0135811015614b0657600080fd5b614b168e60808f01358f0161497f565b909750955060a08d01359450614b2e60c08e01614824565b93508060e08e01351115614b4157600080fd5b50614b528d60e08e01358e01614860565b81935080925050509295989b509295989b9093969950565b60008060008060008060a08789031215614b8357600080fd5b863567ffffffffffffffff811115614b9a57600080fd5b614ba689828a0161497f565b909750955050602087013593506040870135614bc1816147ff565b959894975092956060810135946080909101359350915050565b600060208284031215614bed57600080fd5b8135613ddf816147ff565b600080600080600080600080600060c08a8c031215614c1657600080fd5b893567ffffffffffffffff80821115614c2e57600080fd5b614c3a8d838e0161497f565b909b50995060208c0135985060408c0135915080821115614c5a57600080fd5b614c668d838e0161497f565b909850965060608c0135955060808c01359150614c82826147ff565b90935060a08b01359080821115614c9857600080fd5b50614ca58c828d01614860565b915080935050809150509295985092959850929598565b60008060008060608587031215614cd257600080fd5b843567ffffffffffffffff811115614ce957600080fd5b614cf58782880161497f565b909550935050602085013591506040850135614d10816147ff565b939692955090935050565b600080600080600080600060e0888a031215614d3657600080fd5b8735614d41816147ff565b96506020880135614d51816147ff565b95506040880135945060608801359350608088013560ff81168114614d7557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215614da557600080fd5b8235915060208301358015158114614dbc57600080fd5b809150509250929050565b60008060408385031215614dda57600080fd5b8235614de5816147ff565b91506020830135614dbc816147ff565b600080600080600060808688031215614e0d57600080fd5b853567ffffffffffffffff811115614e2457600080fd5b614e308882890161497f565b909650945050602086013592506040860135614e4b816147ff565b91506060860135614e5b816147ff565b809150509295509295909350565b600080600080600080600060c0888a031215614e8457600080fd5b873567ffffffffffffffff811115614e9b57600080fd5b614ea78a828b0161497f565b909850965050602088013594506040880135614ec2816147ff565b93506060880135614ed2816147ff565b969995985093969295946080840135945060a09093013592915050565b600060208284031215614f0157600080fd5b5051919050565b600181811c90821680614f1c57607f821691505b602082108103614f55577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610bd757610bd7614f5b565b8082028115828204841417610bd757610bd7614f5b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215614ff557600080fd5b8151613ddf816147ff565b80820180821115610bd757610bd7614f5b565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361504457615044614f5b565b5060010190565b60007f8000000000000000000000000000000000000000000000000000000000000000820361507c5761507c614f5b565b5060000390565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b6060815283606082015260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85111561510957600080fd5b8460051b8087608085013760208301949094525060408101919091520160800192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261516d5761516d61512f565b500490565b6000826151815761518161512f565b500690565b6dffffffffffffffffffffffffffff8281168282160390808211156151ad576151ad614f5b565b5092915050565b6dffffffffffffffffffffffffffff8181168382160190808211156151ad576151ad614f5b565b808201828112600083128015821682158216171561133757611337614f5b565b81810360008312801583831316838312821617156151ad576151ad614f5b565b600080835481600182811c91508083168061523757607f831692505b6020808410820361526f577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b81801561528357600181146152b6576152e3565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00861689528415158502890196506152e3565b60008a81526020902060005b868110156152db5781548b8201529085019083016152c2565b505084890196505b509498975050505050505050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff82811682821681810283169291811582850482141761532f5761532f614f5b565b50505092915050565b60007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff808416806153675761536761512f565b9216919091049291505056fea26469706673582212209ebcfe95f38ff8cbee90ed6a205481250d0f29ff88e5e78c8b7b6be524f6fbce64736f6c6343000811003300000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a80000000000000000000000000cc56e024e9fda80f939ab3b434d0dd76765d1750000000000000000000000000cc56e024e9fda80f939ab3b434d0dd76765d175

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106102e95760003560e01c806370a0823111610191578063bc25cf77116100e3578063d99f958f11610097578063ec5357c611610071578063ec5357c61461075d578063f46901ed14610770578063fca3b5aa1461078357600080fd5b8063d99f958f1461070c578063dd62ed3e1461071f578063df4209ed1461074a57600080fd5b8063c24317ff116100c8578063c24317ff146106a1578063c5700a02146106b4578063d505accf146106f957600080fd5b8063bc25cf7714610686578063bcf90d2b1461069957600080fd5b806395d89b4111610145578063a752d9aa1161011f578063a752d9aa14610657578063a9059cbb14610660578063b590bc621461067357600080fd5b806395d89b41146106235780639a31e7e31461062b578063a370c6681461063457600080fd5b80637ecebe00116101765780637ecebe00146105c4578063837c7dc2146105e4578063872b9a9a146105f757600080fd5b806370a082311461056b5780637464fc3d1461058b57600080fd5b80631d1c9e591161024a578063313ce567116101fe578063529fb8c1116101d8578063529fb8c1146105295780635922e56f1461053c5780635b01e3741461054f57600080fd5b8063313ce567146104d55780633644e5151461050e57806340874f011461051657600080fd5b806323b872dd1161022f57806323b872dd14610488578063274cdd5c1461049b5780632a01e6f9146104c257600080fd5b80631d1c9e591461044e57806320bb4adf1461047557600080fd5b80630902f1ac116102a157806309c0478c1161028657806309c0478c146103e9578063150b7a02146103f357806318160ddd1461043757600080fd5b80630902f1ac1461039d578063095ea7b3146103c657600080fd5b806306fdde03116102d257806306fdde0314610355578063075461721461036a57806307a2d13a1461038a57600080fd5b8063017e7e58146102ee57806301e1d11414610338575b600080fd5b60075461030e9073ffffffffffffffffffffffffffffffffffffffff1681565b60405173ffffffffffffffffffffffffffffffffffffffff90911681526020015b60405180910390f35b610340610796565b6040805192835260208301919091520161032f565b61035d610900565b60405161032f919061477a565b60085461030e9073ffffffffffffffffffffffffffffffffffffffff1681565b6103406103983660046147e6565b61098e565b6103a56109ca565b60408051938452602084019290925263ffffffff169082015260600161032f565b6103d96103d4366004614834565b610b63565b604051901515815260200161032f565b6103f1610bdd565b005b6104066104013660046148a9565b6110f2565b6040517fffffffff00000000000000000000000000000000000000000000000000000000909116815260200161032f565b61044060035481565b60405190815260200161032f565b61030e7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776981565b61044061048336600461491c565b6112ce565b6103d961049636600461493e565b61133f565b61030e7f000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a881565b6104406104d03660046149c4565b611483565b6104fc7f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff909116815260200161032f565b610440611817565b610440610524366004614a46565b611872565b610440610537366004614a92565b611b44565b61044061054a366004614b6a565b611c73565b6105586103e881565b60405161ffff909116815260200161032f565b610440610579366004614bdb565b60046020526000908152604090205481565b600b546105a5906dffffffffffffffffffffffffffff1681565b6040516dffffffffffffffffffffffffffff909116815260200161032f565b6104406105d2366004614bdb565b60066020526000908152604090205481565b6104406105f2366004614bf8565b611d0c565b600b546105a5906e01000000000000000000000000000090046dffffffffffffffffffffffffffff1681565b61035d61255e565b61044060095481565b6103d96106423660046147e6565b600c6020526000908152604090205460ff1681565b610440600a5481565b6103d961066e366004614834565b61256b565b610440610681366004614a46565b6125f0565b6103f1610694366004614bdb565b6127c1565b61055860c881565b6104406106af366004614cbc565b61299c565b600b546106e4907c0100000000000000000000000000000000000000000000000000000000900463ffffffff1681565b60405163ffffffff909116815260200161032f565b6103f1610707366004614d1b565b612d74565b6103f161071a366004614d92565b613093565b61044061072d366004614dc7565b600560209081526000928352604080842090915290825290205481565b610440610758366004614df5565b613144565b61044061076b366004614e69565b6136cc565b6103f161077e366004614bdb565b613767565b6103f1610791366004614bdb565b613886565b6040517fd075fbba000000000000000000000000000000000000000000000000000000008152306004820152600090819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769169063d075fbba90602401602060405180830381865afa158015610825573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108499190614eef565b6040517f11f93e780000000000000000000000000000000000000000000000000000000081523060048201529092507f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff16906311f93e7890602401602060405180830381865afa1580156108d6573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906108fa9190614eef565b90509091565b6001805461090d90614f08565b80601f016020809104026020016040519081016040528092919081815260200182805461093990614f08565b80156109865780601f1061095b57610100808354040283529160200191610986565b820191906000526020600020905b81548152906001019060200180831161096957829003601f168201915b505050505081565b600354600090819080156109c4576109a4610796565b90935091506109b48484836139a5565b92506109c18483836139a5565b91505b50915091565b6040517fd075fbba0000000000000000000000000000000000000000000000000000000081523060048201526000908190819073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769169063d075fbba90602401602060405180830381865afa158015610a5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a7f9190614eef565b6040517f11f93e780000000000000000000000000000000000000000000000000000000081523060048201529093507f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff16906311f93e7890602401602060405180830381865afa158015610b0c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b309190614eef565b600b54939490937c0100000000000000000000000000000000000000000000000000000000900463ffffffff1692509050565b33600081815260056020908152604080832073ffffffffffffffffffffffffffffffffffffffff8716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92590610bcb9086815260200190565b60405180910390a35060015b92915050565b600054600114610c4e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e43590000000000000000000000000000000000000000000060448201526064015b60405180910390fd5b600260005560085473ffffffffffffffffffffffffffffffffffffffff163314610cc6576008546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b60007f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff166310f255f56040518163ffffffff1660e01b8152600401602060405180830381865afa158015610d33573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610d579190614eef565b6040517fd075fbba00000000000000000000000000000000000000000000000000000000815230600482015290915060009073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769169063d075fbba90602401602060405180830381865afa158015610de7573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610e0b9190614eef565b6040517f11f93e7800000000000000000000000000000000000000000000000000000000815230600482015290915060009073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776916906311f93e7890602401602060405180830381865afa158015610e9b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ebf9190614eef565b90508160008080610ed18486896139e1565b91945092509050821515600003610f1e576040517fde8591610000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610c45565b82156110d5576040517fc9bddac600000000000000000000000000000000000000000000000000000000815260048101889052600160248201527f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff169063c9bddac6906044016020604051808303816000875af1158015610fb8573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fdc9190614eef565b50610fe78785614f8a565b604080518481526020810184905290810189905290945033907ff79584ad48f1b6f4760ff2c645a0bc47324f626fb535ad3aa1a4f7e49525651a9060600160405180910390a27f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff166310f255f56040518163ffffffff1660e01b8152600401602060405180830381865afa158015611098573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906110bc9190614eef565b96506110c98486896139e1565b91945092509050610f1e565b6110e484868888600180613a81565b505060016000555050505050565b60003373ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f3877691614611163576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000848152600c602052604090205460ff1615156001036111b0576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040517ffa522a15000000000000000000000000000000000000000000000000000000008152600481018590526000907f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff169063fa522a1590602401602060405180830381865afa15801561123e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906112629190614eef565b905060068110156112a2576040517f9f44c9eb00000000000000000000000000000000000000000000000000000000815260048101869052602401610c45565b507f150b7a02000000000000000000000000000000000000000000000000000000009695505050505050565b600354600090816112e76112e28587614f9d565b613d18565b90508115611333576000806112fa610796565b9092509050600061130e6112e28385614f9d565b9050600061131c8583613dca565b90506113288682613de6565b965050505050611337565b8092505b505092915050565b73ffffffffffffffffffffffffffffffffffffffff831660009081526005602090815260408083203384529091528120547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146113d3576113a18382614f8a565b73ffffffffffffffffffffffffffffffffffffffff861660009081526005602090815260408083203384529091529020555b73ffffffffffffffffffffffffffffffffffffffff851660009081526004602052604081208054859290611408908490614f8a565b909155505073ffffffffffffffffffffffffffffffffffffffff808516600081815260046020526040908190208054870190555190918716907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906114709087815260200190565b60405180910390a3506001949350505050565b60008060006114906109ca565b50909250905060006114a28584614f8a565b905081600080805b898110156116eb57307f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff16636352211e8d8d8581811061150157611501614fb4565b905060200201356040518263ffffffff1660e01b815260040161152691815260200190565b602060405180830381865afa158015611543573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906115679190614fe3565b73ffffffffffffffffffffffffffffffffffffffff16146115b4576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1663fa522a158c8c8481811061160257611602614fb4565b905060200201356040518263ffffffff1660e01b815260040161162791815260200190565b602060405180830381865afa158015611644573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116689190614eef565b915060068210156116c1578a8a8281811061168557611685614fb4565b905060200201356040517f9f44c9eb000000000000000000000000000000000000000000000000000000008152600401610c4591815260200190565b6116cb8285614f8a565b93506116d78284615000565b9250806116e381615013565b9150506114aa565b506116f68b85615000565b935060005b8c8110156117f2577f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1663fa522a158f8f8481811061175157611751614fb4565b905060200201356040518263ffffffff1660e01b815260040161177691815260200190565b602060405180830381865afa158015611793573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906117b79190614eef565b915060068210156117d4578d8d8281811061168557611685614fb4565b6117de8285615000565b9350806117ea81615013565b9150506116fb565b50611803868686868c876000613dfb565b50909e9d5050505050505050505050505050565b60007f0000000000000000000000000000000000000000000000000000000000000001461461184d57611848614001565b905090565b507fe7426cc538502e4c324d4327fc221585b9be7f643448648e75c94d2bd3cf3f6690565b600080600061187f6109ca565b5060035491935091506000611894848461409b565b5050905080826118a49190615000565b915060006118b28786614f8a565b9050836000805b8a811015611ab257307f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff16636352211e8e8e8581811061191057611910614fb4565b905060200201356040518263ffffffff1660e01b815260040161193591815260200190565b602060405180830381865afa158015611952573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906119769190614fe3565b73ffffffffffffffffffffffffffffffffffffffff16146119c3576040517f0f3e2a3e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1663fa522a158d8d84818110611a1157611a11614fb4565b905060200201356040518263ffffffff1660e01b8152600401611a3691815260200190565b602060405180830381865afa158015611a53573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a779190614eef565b91506006821015611a94578b8b8281811061168557611685614fb4565b611a9e8284614f8a565b925080611aaa81615013565b9150506118b9565b506000611abf8388614f8a565b90506000811180611ad0575060008a115b611b06576040517ff94cc92a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6000611b238585611b1a6112e28c8e614f9d565b60006001614180565b509350505050611b338782614285565b9d9c50505050505050505050505050565b60008a80421115611b8a576040517faa2fd92500000000000000000000000000000000000000000000000000000000815242600482015260248101829052604401610c45565b611b988b8b8b8b8b8b611483565b91506000821215611c06576000611bae8361504b565b90508d811115611bf4576040517fd1d03be700000000000000000000000000000000000000000000000000000000815260048101829052602481018f9052604401610c45565b611bfe8188615000565b965050611c62565b6000821315611c6257818d811115611c54576040517fd1d03be700000000000000000000000000000000000000000000000000000000815260048101829052602481018f9052604401610c45565b611c5e818b615000565b9950505b611b338b8b8b8b8b8b8b8b8b611d0c565b60008180421115611cb9576040517faa2fd92500000000000000000000000000000000000000000000000000000000815242600482015260248101829052604401610c45565b611cc58888888861299c565b915083821015611d01576040517f40ff566600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b509695505050505050565b60008054600114611d79576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b600260005584151580611d8b57508515155b611dcb576040517fd28d3eb50000000000000000000000000000000000000000000000000000000081526004810186905260248101879052604401610c45565b7f000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a873ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff161480611e7057507f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16145b15611ebf576040517f9cfea58300000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff85166004820152602401610c45565b60006040518061010001604052806000815260200160008152602001600081526020016000815260200160008152602001600081526020016000815260200160008152509050611f0d6109ca565b50602083015281528515611ffe576040517f9cc397fb000000000000000000000000000000000000000000000000000000008152600481018790527f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1690639cc397fb90602401600060405180830381600087803b158015611fa357600080fd5b505af1158015611fb7573d6000803e3d6000fd5b50611ffe92505073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a8169050868861429a565b86156121f15760005b878110156121ef5760007f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1663fa522a158b8b8581811061205f5761205f614fb4565b905060200201356040518263ffffffff1660e01b815260040161208491815260200190565b602060405180830381865afa1580156120a1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906120c59190614eef565b905060068110156120e25789898381811061168557611685614fb4565b80836080018181516120f49190615000565b90525073ffffffffffffffffffffffffffffffffffffffff7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769166323b872dd30898d8d8781811061214757612147614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b1580156121c357600080fd5b505af11580156121d7573d6000803e3d6000fd5b505050505080806121e790615013565b915050612007565b505b821561227e576040517fe53e56d500000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff86169063e53e56d59061224b9087908790600401615083565b600060405180830381600087803b15801561226557600080fd5b505af1158015612279573d6000803e3d6000fd5b505050505b8815612367576122c673ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a81633308c614359565b6040517f0b38049f000000000000000000000000000000000000000000000000000000008152600481018a90527f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1690630b38049f90602401600060405180830381600087803b15801561234e57600080fd5b505af1158015612362573d6000803e3d6000fd5b505050505b60005b8a811015612469577f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff166342842e0e33308f8f868181106123c2576123c2614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b15801561243e57600080fd5b505af1158015612452573d6000803e3d6000fd5b50505050808061246190615013565b91505061236a565b506124726109ca565b5060608301819052604083018290528251602084015160808501516124a0949293919291908b906001613dfb565b60c084015260a083015260e082015260408101516060820151825160208401516124cf93929190600080613a81565b60a081015160c0820151608083015160405173ffffffffffffffffffffffffffffffffffffffff89169333937fb3e2773606abfd36b5bd91394b3a54d1398336c65005baf7bf7a05efeffaf75b9361253f938d919093845260208401929092526040830152606082015260800190565b60405180910390a360e0015160016000559a9950505050505050505050565b6002805461090d90614f08565b3360009081526004602052604081208054839190839061258c908490614f8a565b909155505073ffffffffffffffffffffffffffffffffffffffff8316600081815260046020526040908190208054850190555133907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90610bcb9086815260200190565b60008060006125fd6109ca565b5091509150600061260e838361409b565b505090506000600354826126229190615000565b905060006126308588615000565b90508360005b89811015612710577f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1663fa522a158c8c8481811061268c5761268c614fb4565b905060200201356040518263ffffffff1660e01b81526004016126b191815260200190565b602060405180830381865afa1580156126ce573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906126f29190614eef565b6126fc9083615000565b91508061270881615013565b915050612636565b506000806127268484611b1a6112e28b8d614f9d565b5093505050915084600003612757576103e861274683633b9aca00614f9d565b6127509190614f8a565b9850612764565b6127618582613de6565b98505b8860000361279e576040517fd04ebef000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6127a78961441f565b6127b1908a614f8a565b9c9b505050505050505050505050565b60005460011461282d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b600260005560075473ffffffffffffffffffffffffffffffffffffffff1633146128a5576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015260009073ffffffffffffffffffffffffffffffffffffffff8316906370a0823190602401602060405180830381865afa158015612912573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906129369190614eef565b905080600003612972576040517f368a711600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61299373ffffffffffffffffffffffffffffffffffffffff8316338361429a565b50506001600055565b60008054600114612a09576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b6002600090815580612a196109ca565b5091509150612a288282614439565b508415612b1257612a7173ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a816333088614359565b6040517f0b38049f000000000000000000000000000000000000000000000000000000008152600481018690527f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1690630b38049f90602401600060405180830381600087803b158015612af957600080fd5b505af1158015612b0d573d6000803e3d6000fd5b505050505b60005b86811015612c14577f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff166342842e0e33308b8b86818110612b6d57612b6d614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b158015612be957600080fd5b505af1158015612bfd573d6000803e3d6000fd5b505050508080612c0c90615013565b915050612b15565b50600080612c206109ca565b5060035491935091506000612c386112e28688614f9d565b9050600080612c4c86868560006001614180565b5093505050915083600003612c7d576103e8612c6c83633b9aca00614f9d565b612c769190614f8a565b9850612c8a565b612c878482613de6565b98505b88600003612cc4576040517fd04ebef000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50505050612cd185614545565b612cdb9086614f8a565b9450612ce786866145d7565b612cf78282868660006001613a81565b8573ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f06caae2b2e26a58cd4700e53b62553aa8689e11ecf1506dbc680a1cc615306c38b8b8b8a604051612d5a94939291906150d0565b60405180910390a350506001600055509095945050505050565b42841015612dde576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f5045524d49545f444541444c494e455f455850495245440000000000000000006044820152606401610c45565b60006001612dea611817565b73ffffffffffffffffffffffffffffffffffffffff8a811660008181526006602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e0830190915280519201919091207f190100000000000000000000000000000000000000000000000000000000000061010083015261010282019290925261012281019190915261014201604080517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe08184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa158015612f3c573d6000803e3d6000fd5b50506040517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0015191505073ffffffffffffffffffffffffffffffffffffffff811615801590612fb757508773ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16145b61301d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f494e56414c49445f5349474e45520000000000000000000000000000000000006044820152606401610c45565b73ffffffffffffffffffffffffffffffffffffffff90811660009081526005602090815260408083208a8516808552908352928190208990555188815291928a16917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a350505050505050565b60075473ffffffffffffffffffffffffffffffffffffffff163314613106576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b6000918252600c602052604090912080547fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0016911515919091179055565b600080546001146131b1576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f5245454e5452414e4359000000000000000000000000000000000000000000006044820152606401610c45565b60026000908155806131c16109ca565b50909250905081816131d38282614439565b5087156132ca576040517f9cc397fb000000000000000000000000000000000000000000000000000000008152600481018990527f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1690639cc397fb90602401600060405180830381600087803b15801561326257600080fd5b505af1158015613276573d6000803e3d6000fd5b506132bd92505073ffffffffffffffffffffffffffffffffffffffff7f000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a8169050888a61429a565b6132c78883614f8a565b91505b6000805b8a8110156134ab577f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff1663fa522a158d8d8481811061332457613324614fb4565b905060200201356040518263ffffffff1660e01b815260040161334991815260200190565b602060405180830381865afa158015613366573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061338a9190614eef565b915060068210156133a7578b8b8281811061168557611685614fb4565b7f00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f38776973ffffffffffffffffffffffffffffffffffffffff166323b872dd308b8f8f868181106133f7576133f7614fb4565b6040517fffffffff0000000000000000000000000000000000000000000000000000000060e088901b16815273ffffffffffffffffffffffffffffffffffffffff958616600482015294909316602485015250602090910201356044820152606401600060405180830381600087803b15801561347357600080fd5b505af1158015613487573d6000803e3d6000fd5b5050505081836134979190614f8a565b9250806134a381615013565b9150506132ce565b5060006134b88386614f8a565b905060008111806134c9575060008a115b6134ff576040517ff94cc92a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60006135138585611b1a6112e28a8c614f9d565b5093505050506000600354905061352a8183614285565b9850505073ffffffffffffffffffffffffffffffffffffffff8816331461361b5773ffffffffffffffffffffffffffffffffffffffff88166000908152600560209081526040808320338452909152902054878110156135b6576040517f13be252b00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114613619576135e78882614f8a565b73ffffffffffffffffffffffffffffffffffffffff8a1660009081526005602090815260408083203384529091529020555b505b6136258888614650565b6136358484888860006001613a81565b8773ffffffffffffffffffffffffffffffffffffffff168973ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f1219fadb6b0ead011622d9ff05712029b1ba5ae340a49f25c9b5607a94a842c48f8f8f8d6040516136af94939291906150d0565b60405180910390a450506001600055509298975050505050505050565b60008180421115613712576040517faa2fd92500000000000000000000000000000000000000000000000000000000815242600482015260248101829052604401610c45565b61371f8989898989613144565b91508382111561375b576040517f6ca104df00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50979650505050505050565b60075473ffffffffffffffffffffffffffffffffffffffff1633146137da576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b73ffffffffffffffffffffffffffffffffffffffff811661383f576040517f8e4c8aa600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c45565b600780547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60075473ffffffffffffffffffffffffffffffffffffffff1633146138f9576007546040517f0cfe98f700000000000000000000000000000000000000000000000000000000815233600482015273ffffffffffffffffffffffffffffffffffffffff9091166024820152604401610c45565b73ffffffffffffffffffffffffffffffffffffffff811661395e576040517f8e4c8aa600000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff82166004820152602401610c45565b600880547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff04841183021582026139da57600080fd5b5091020490565b600080808515806139f0575084155b15613a31576040517fa17e11d50000000000000000000000000000000000000000000000000000000081526004810187905260248101869052604401610c45565b83600003613a425760019250613a78565b83861115613a785762011e4e613a5a61271086614f9d565b613a64919061515e565b9150613a70858761515e565b905081811192505b93509350939050565b6000613a9264010000000042615172565b600b5490915063ffffffff7c0100000000000000000000000000000000000000000000000000000000909104811682039086908690831615801590613ad657508715155b8015613ae157508615155b15613b8b578263ffffffff16613b1e82613afa856146de565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1690614709565b600980547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff929092169290920201905563ffffffff8316613b5e83613afa846146de565b600a80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff92909216929092020190555b600b80547bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167c010000000000000000000000000000000000000000000000000000000063ffffffff8716021790558480613bdf5750855b15613cd3576000613bf36112e28b8d614f9d565b600b549091506dffffffffffffffffffffffffffff90811690821681118015613c195750875b15613c8f57613c288282615186565b600b8054600e90613c5c9084906e01000000000000000000000000000090046dffffffffffffffffffffffffffff166151b4565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff1602179055505b8615613cd057600b80547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000166dffffffffffffffffffffffffffff84161790555b50505b604080518b8152602081018b90527fcf2aa50876cdfbb541206f89af0ee78d44a2abf8d328e37fa4917f982149848a910160405180910390a150505050505050505050565b60b581710100000000000000000000000000000000008110613d3f5760409190911b9060801c5b69010000000000000000008110613d5b5760209190911b9060401c5b650100000000008110613d735760109190911b9060201c5b63010000008110613d895760089190911b9060101c5b62010000010260121c80820401600190811c80830401811c80830401811c80830401811c80830401811c80830401811c80830401901c908190048111900390565b6000613ddf83670de0b6b3a7640000846139a5565b9392505050565b6000613ddf8383670de0b6b3a76400006139a5565b60008080613e09868b614f8a565b8811613e16576000613e2a565b613e20868b614f8a565b613e2a9089614f8a565b9150613e36858a614f8a565b8711613e43576000613e57565b613e4d858a614f8a565b613e579088614f8a565b90506000821180613e685750600081115b613ea8576040517fec3e79fb0000000000000000000000000000000000000000000000000000000081526004810183905260248101829052604401610c45565b6000613eb5836003614f9d565b613ec18a6103e8614f9d565b613ecb9190614f8a565b90506000613eda836003614f9d565b613ee68a6103e8614f9d565b613ef09190614f8a565b90506000613efe8284614f9d565b90506000613f0c8d8f614f9d565b613f1990620f4240614f9d565b905080821015613faf576000613f52613f3986613f40613f398689614725565b6001614285565b613f4a9190614f8a565b6103e5614725565b90508815613f9d576040517ffa251a69000000000000000000000000000000000000000000000000000000008152600481018290526024810184905260448101839052606401610c45565b613fa781896151db565b975050613ff0565b80821115613ff057613fe3613fdc613fca613f398487614725565b613fd49087614f8a565b6103e8613dca565b6001613de6565b613fed90886151fb565b96505b505050509750975097945050505050565b60007f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f6001604051614033919061521b565b6040805191829003822060208301939093528101919091527fc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc660608201524660808201523060a082015260c00160405160208183030381529060405280519060200120905090565b600b54600090819081906dffffffffffffffffffffffffffff808216916e0100000000000000000000000000009004168115614177576000806000806141058b8b886dffffffffffffffffffffffffffff16886dffffffffffffffffffffffffffff166000614180565b94509450945094505060008411801561411b5750825b1561417257600354612710906103e8906141359085613de6565b61413f9190614f9d565b614149919061515e565b9850816dffffffffffffffffffffffffffff169650806dffffffffffffffffffffffffffff1697505b505050505b50509250925092565b6000808080806141936112e28a8c614f9d565b9450846000036141d9576040517f83cb13eb000000000000000000000000000000000000000000000000000000008152600481018b9052602481018a9052604401610c45565b505050848211600080826141f6576141f18589614f8a565b614200565b6142008886614f8a565b9350871561426a57821561424957861561424957868411614230576142258482615000565b905060009350614249565b61423a8782615000565b90506142468785614f8a565b93505b8515614260576142598489614725565b9150614278565b6142598489613dca565b614275600180614725565b91505b9550955095509550959050565b6000613ddf8383670de0b6b3a7640000614736565b60006040517fa9059cbb000000000000000000000000000000000000000000000000000000008152836004820152826024820152602060006044836000895af13d15601f3d1160016000511416171691505080614353576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600f60248201527f5452414e534645525f4641494c454400000000000000000000000000000000006044820152606401610c45565b50505050565b60006040517f23b872dd0000000000000000000000000000000000000000000000000000000081528460048201528360248201528260448201526020600060648360008a5af13d15601f3d1160016000511416171691505080614418576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f5452414e534645525f46524f4d5f4641494c45440000000000000000000000006044820152606401610c45565b5050505050565b600061271061442f60c884614f9d565b610bd7919061515e565b600080600080614449868661409b565b9194509250905081156144b45781600b600e8282829054906101000a90046dffffffffffffffffffffffffffff166144819190615186565b92506101000a8154816dffffffffffffffffffffffffffff02191690836dffffffffffffffffffffffffffff1602179055505b821561453b576007546144dd9073ffffffffffffffffffffffffffffffffffffffff16846145d7565b600754604080518581526001602082015290810183905273ffffffffffffffffffffffffffffffffffffffff909116907f5623b32a5de3b9c9ab0cc28fb483268639c2a9b4fef0da1c5a7cdff50da7e4859060600160405180910390a25b5090949350505050565b60006145508261441f565b6007549091506145769073ffffffffffffffffffffffffffffffffffffffff16826145d7565b6007546040805183815260006020820181905281830152905173ffffffffffffffffffffffffffffffffffffffff909216917f5623b32a5de3b9c9ab0cc28fb483268639c2a9b4fef0da1c5a7cdff50da7e4859181900360600190a2919050565b80600360008282546145e99190615000565b909155505073ffffffffffffffffffffffffffffffffffffffff82166000818152600460209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef91015b60405180910390a35050565b73ffffffffffffffffffffffffffffffffffffffff821660009081526004602052604081208054839290614685908490614f8a565b909155505060038054829003905560405181815260009073ffffffffffffffffffffffffffffffffffffffff8416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001614644565b6000610bd76e0100000000000000000000000000006dffffffffffffffffffffffffffff84166152f1565b6000613ddf6dffffffffffffffffffffffffffff831684615338565b6000613ddf83670de0b6b3a7640000845b6000827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048411830215820261476b57600080fd5b50910281810615159190040190565b600060208083528351808285015260005b818110156147a75785810183015185820160400152820161478b565b5060006040828601015260407fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f8301168501019250505092915050565b6000602082840312156147f857600080fd5b5035919050565b73ffffffffffffffffffffffffffffffffffffffff8116811461482157600080fd5b50565b803561482f816147ff565b919050565b6000806040838503121561484757600080fd5b8235614852816147ff565b946020939093013593505050565b60008083601f84011261487257600080fd5b50813567ffffffffffffffff81111561488a57600080fd5b6020830191508360208285010111156148a257600080fd5b9250929050565b6000806000806000608086880312156148c157600080fd5b85356148cc816147ff565b945060208601356148dc816147ff565b935060408601359250606086013567ffffffffffffffff8111156148ff57600080fd5b61490b88828901614860565b969995985093965092949392505050565b6000806040838503121561492f57600080fd5b50508035926020909101359150565b60008060006060848603121561495357600080fd5b833561495e816147ff565b9250602084013561496e816147ff565b929592945050506040919091013590565b60008083601f84011261499157600080fd5b50813567ffffffffffffffff8111156149a957600080fd5b6020830191508360208260051b85010111156148a257600080fd5b600080600080600080608087890312156149dd57600080fd5b863567ffffffffffffffff808211156149f557600080fd5b614a018a838b0161497f565b9098509650602089013595506040890135915080821115614a2157600080fd5b50614a2e89828a0161497f565b979a9699509497949695606090950135949350505050565b600080600060408486031215614a5b57600080fd5b833567ffffffffffffffff811115614a7257600080fd5b614a7e8682870161497f565b909790965060209590950135949350505050565b60008060008060008060008060008060006101008c8e031215614ab457600080fd5b8b359a5060208c0135995067ffffffffffffffff8060408e01351115614ad957600080fd5b614ae98e60408f01358f0161497f565b909a50985060608d0135975060808d0135811015614b0657600080fd5b614b168e60808f01358f0161497f565b909750955060a08d01359450614b2e60c08e01614824565b93508060e08e01351115614b4157600080fd5b50614b528d60e08e01358e01614860565b81935080925050509295989b509295989b9093969950565b60008060008060008060a08789031215614b8357600080fd5b863567ffffffffffffffff811115614b9a57600080fd5b614ba689828a0161497f565b909750955050602087013593506040870135614bc1816147ff565b959894975092956060810135946080909101359350915050565b600060208284031215614bed57600080fd5b8135613ddf816147ff565b600080600080600080600080600060c08a8c031215614c1657600080fd5b893567ffffffffffffffff80821115614c2e57600080fd5b614c3a8d838e0161497f565b909b50995060208c0135985060408c0135915080821115614c5a57600080fd5b614c668d838e0161497f565b909850965060608c0135955060808c01359150614c82826147ff565b90935060a08b01359080821115614c9857600080fd5b50614ca58c828d01614860565b915080935050809150509295985092959850929598565b60008060008060608587031215614cd257600080fd5b843567ffffffffffffffff811115614ce957600080fd5b614cf58782880161497f565b909550935050602085013591506040850135614d10816147ff565b939692955090935050565b600080600080600080600060e0888a031215614d3657600080fd5b8735614d41816147ff565b96506020880135614d51816147ff565b95506040880135945060608801359350608088013560ff81168114614d7557600080fd5b9699959850939692959460a0840135945060c09093013592915050565b60008060408385031215614da557600080fd5b8235915060208301358015158114614dbc57600080fd5b809150509250929050565b60008060408385031215614dda57600080fd5b8235614de5816147ff565b91506020830135614dbc816147ff565b600080600080600060808688031215614e0d57600080fd5b853567ffffffffffffffff811115614e2457600080fd5b614e308882890161497f565b909650945050602086013592506040860135614e4b816147ff565b91506060860135614e5b816147ff565b809150509295509295909350565b600080600080600080600060c0888a031215614e8457600080fd5b873567ffffffffffffffff811115614e9b57600080fd5b614ea78a828b0161497f565b909850965050602088013594506040880135614ec2816147ff565b93506060880135614ed2816147ff565b969995985093969295946080840135945060a09093013592915050565b600060208284031215614f0157600080fd5b5051919050565b600181811c90821680614f1c57607f821691505b602082108103614f55577f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b81810381811115610bd757610bd7614f5b565b8082028115828204841417610bd757610bd7614f5b565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600060208284031215614ff557600080fd5b8151613ddf816147ff565b80820180821115610bd757610bd7614f5b565b60007fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361504457615044614f5b565b5060010190565b60007f8000000000000000000000000000000000000000000000000000000000000000820361507c5761507c614f5b565b5060000390565b60208152816020820152818360408301376000818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b6060815283606082015260007f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff85111561510957600080fd5b8460051b8087608085013760208301949094525060408101919091520160800192915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b60008261516d5761516d61512f565b500490565b6000826151815761518161512f565b500690565b6dffffffffffffffffffffffffffff8281168282160390808211156151ad576151ad614f5b565b5092915050565b6dffffffffffffffffffffffffffff8181168382160190808211156151ad576151ad614f5b565b808201828112600083128015821682158216171561133757611337614f5b565b81810360008312801583831316838312821617156151ad576151ad614f5b565b600080835481600182811c91508083168061523757607f831692505b6020808410820361526f577f4e487b710000000000000000000000000000000000000000000000000000000086526022600452602486fd5b81801561528357600181146152b6576152e3565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00861689528415158502890196506152e3565b60008a81526020902060005b868110156152db5781548b8201529085019083016152c2565b505084890196505b509498975050505050505050565b7bffffffffffffffffffffffffffffffffffffffffffffffffffffffff82811682821681810283169291811582850482141761532f5761532f614f5b565b50505092915050565b60007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff808416806153675761536761512f565b9216919091049291505056fea26469706673582212209ebcfe95f38ff8cbee90ed6a205481250d0f29ff88e5e78c8b7b6be524f6fbce64736f6c63430008110033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a80000000000000000000000000cc56e024e9fda80f939ab3b434d0dd76765d1750000000000000000000000000cc56e024e9fda80f939ab3b434d0dd76765d175

-----Decoded View---------------
Arg [0] : _gobblersAddress (address): 0x60bb1e2AA1c9ACAfB4d34F71585D7e959f387769
Arg [1] : _gooAddress (address): 0x600000000a36F3cD48407e35eB7C5c910dc1f7a8
Arg [2] : _feeTo (address): 0x0Cc56E024E9FDa80F939aB3b434d0DD76765d175
Arg [3] : _minter (address): 0x0Cc56E024E9FDa80F939aB3b434d0DD76765d175

-----Encoded View---------------
4 Constructor Arguments found :
Arg [0] : 00000000000000000000000060bb1e2aa1c9acafb4d34f71585d7e959f387769
Arg [1] : 000000000000000000000000600000000a36f3cd48407e35eb7c5c910dc1f7a8
Arg [2] : 0000000000000000000000000cc56e024e9fda80f939ab3b434d0dd76765d175
Arg [3] : 0000000000000000000000000cc56e024e9fda80f939ab3b434d0dd76765d175


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.