// File: UintsLotteryV3.sol



// SPDX-License-Identifier: MPL-2.0
// Numbers are art, we are artists
// Creator: wgw.eth / @wgw_eth / @tunnckoCore

pragma solidity ^0.8.18;

import "./IUINTS721.sol";

error Unauthorized();
error InvalidTokenId();
error InvalidTokenIdStart(uint tokenIdStart);
error InvalidSignature();
error InvalidSignerOrSignature();
error MinGreaterThanMax();
error RoundHasEnded();
error RoundHasNotStarted();
error RoundHasNotEnded(uint roundId);
error AtLeastOneTokenNeeded();
error MaxTokensPerRoundPerAddressExceeded();
error NotOwnerOfTokens();
error FailedToChooseWinnerTryAgain();
error GameIsPaused();

contract UintsLotteryV3 {
    uint256 public immutable deployedTimestamp = block.timestamp;
    uint256 public immutable deployedBlock = block.number;
    address public deployer = msg.sender;
    bytes private _deployerSeedSig;

    // tokenId => owner
    mapping(uint => address) private _tokenOwners;

    // roundId => tokenIds
    mapping(uint => uint[]) private _roundTokenIds;

    // roundId => address => num deposited
    mapping(uint => mapping(address => uint))
        public numDepositedPerRoundPerAddress;

    bytes[] private _signatures;

    address private wgwAddress = 0xA20C07F94A127fD76E61fbeA1019cCe759225002;

    // UINTS mainnet contract
    IUINTS721 private nftContract =
        IUINTS721(0x7C10C8816575e8Fdfb11463dD3811Cc794A1D407);

    uint public roundId = 0;
    uint public nonce = 59;

    uint private _maxPerRoundPerAddress = 30;
    uint private _numPerRound;
    uint private _numToDeployer;
    uint private _numToLowestIdOwner;
    uint private _numToCombine;
    uint private _minTokenIdStart = 10000;

    // 0 = initial
    // 1 = open
    // 2 = closed
    // 3 = paused
    uint public roundState;

    event RoundCreated(
        uint indexed roundId,
        uint numPerRound,
        uint numToDeployer,
        uint numToLowestIdOwner,
        uint indexed numToCombine,
        uint indexed minTokenIdStart
    );
    event RoundJoined(
        uint indexed roundId,
        bytes signature,
        string message,
        uint[] indexed roundTokenIds,
        uint[] tokenIds,
        uint numDeposited
    );
    event RoundClosed(
        uint indexed roundId,
        bytes signature,
        string message,
        address indexed winner,
        uint[] tokensToCombine,
        uint[] tokensToDeployer,
        uint[] tokensToLowestIdOwner
    );

    modifier onlyDeployer() {
        if (msg.sender != deployer) revert Unauthorized();
        _;
    }

    modifier onlyWgw() {
        if (msg.sender != wgwAddress) revert Unauthorized();
        _;
    }

    constructor(bytes memory deployerSignature) {
        _deployerSeedSig = deployerSignature;
    }

    function updateDeployerAddress(
        address deployerVault
    ) external onlyDeployer {
        deployer = deployerVault;
    }

    function pauseGame() external onlyWgw {
        roundState = 3;
        _returnAllTokens();
    }

    function unpauseGame() external onlyWgw {
        roundState = 0;
    }

    function updateNftContract(IUINTS721 _nftContract) external onlyDeployer {
        nftContract = IUINTS721(_nftContract);
        nftContract.setApprovalForAll(address(this), true);
    }

    function updateMaxPerRoundPerAddress(
        uint maxPerRoundPerAddress
    ) external onlyDeployer {
        _maxPerRoundPerAddress = maxPerRoundPerAddress;
    }

    function createRound(uint numPerRound) external {
        _createRound(numPerRound, 3, 5, 10000);
    }

    /**
     * @dev anyone can start a round that want tokenIds >= 8888
     */
    function createRound(uint numPerRound, uint minTokenIdStart) external {
        _createRound(numPerRound, 3, 5, minTokenIdStart);
    }

    /**
     * @dev Create a round with custom parameters,
     * only deployer can start fully customizable rounds.
     */
    function createCustomRound(
        uint numPerRound,
        uint numToDeployer,
        uint numToLowestIdOwner,
        uint idStart
    ) external onlyDeployer {
        _createRound(numPerRound, numToDeployer, numToLowestIdOwner, idStart);
    }

    function _createRound(
        uint numPerRound,
        uint numToDeployer,
        uint numToLowestIdOwner,
        uint minTokenIdStart
    ) private {
        if (roundState == 3) {
            revert GameIsPaused();
        }
        if (roundState != 0 && roundState != 2) {
            revert RoundHasNotEnded(roundId);
        }

        roundId = roundId + 1;
        _numPerRound = numPerRound;
        _numToDeployer = numToDeployer;
        _numToLowestIdOwner = numToLowestIdOwner;
        _numToCombine = numPerRound - numToDeployer - numToLowestIdOwner;

        roundState = 1;

        // only the deployer can start rounds with token ids < 8888
        if (msg.sender == deployer) {
            _minTokenIdStart = minTokenIdStart;
        } else if (minTokenIdStart < 8888) {
            revert InvalidTokenIdStart(minTokenIdStart);
        } else {
            // seems like duplicate of the first if, but it's intentional,
            // making it easier when testing
            _minTokenIdStart = minTokenIdStart;
        }

        emit RoundCreated(
            roundId,
            _numPerRound,
            _numToDeployer,
            _numToLowestIdOwner,
            _numToCombine,
            _minTokenIdStart
        );
    }

    /**
     * @dev Use something like https://etherscan.io/verifiedSignatures,
     * to create and verify signatures. The `_message` should match the one you signed.
     */
    function joinRound(
        bytes memory _signature,
        string calldata _message,
        uint[] calldata tokenIds
    ) external {
        if (roundState == 3) {
            revert GameIsPaused();
        }
        if (_recoverSigner(_signature, _message) != msg.sender) {
            revert InvalidSignerOrSignature();
        }
        if (roundState != 1) {
            revert RoundHasEnded();
        }
        if (tokenIds.length < 1) {
            revert AtLeastOneTokenNeeded();
        }
        if (
            numDepositedPerRoundPerAddress[roundId][msg.sender] >
            _maxPerRoundPerAddress
        ) {
            revert MaxTokensPerRoundPerAddressExceeded();
        }

        uint tokensLen = _roundTokenIds[roundId].length;

        if (tokensLen == _numPerRound) {
            revert RoundHasEnded();
        }

        uint nextLen = tokensLen + tokenIds.length;

        if (nextLen > _numPerRound) {
            uint overlimit = nextLen - _numPerRound;
            tokenIds = tokenIds[0:tokenIds.length - overlimit];
        }

        if (tokensLen < _numPerRound) {
            uint xLen = tokenIds.length;
            numDepositedPerRoundPerAddress[roundId][msg.sender] += xLen;

            for (uint i; i < xLen; i = _uncheckedIncrement(i)) {
                uint tokenId = tokenIds[i];
                if (nftContract.ownerOf(tokenId) != msg.sender) {
                    revert NotOwnerOfTokens();
                }
                if (tokenId < _minTokenIdStart) {
                    revert InvalidTokenId();
                }

                _tokenOwners[tokenId] = msg.sender;
                _roundTokenIds[roundId].push(tokenId);

                nftContract.transferFrom(msg.sender, address(this), tokenId);

                _tokenOwners[tokenId] = msg.sender;
            }
        }

        _signatures.push(_signature);

        emit RoundJoined(
            roundId,
            _signature,
            _message,
            _roundTokenIds[roundId],
            tokenIds,
            numDepositedPerRoundPerAddress[roundId][msg.sender]
        );
    }

    /**
     * @dev Use something like https://etherscan.io/verifiedSignatures,
     * to create and verify signatures. The `_message` should match the one you signed.
     *
     * This method splits the round's tokens into 3 parts:
     * - X tokens to the deployer (default is 3)
     * - X tokens to the address that deposited the lowest tokenId (default is 5)
     * - the rest of the tokens are combined and sent to the winner of the round
     */
    function closeRound(
        bytes memory _signature,
        string calldata _message
    ) external {
        if (roundState == 3) {
            revert GameIsPaused();
        }

        if (_recoverSigner(_signature, _message) != msg.sender) {
            revert InvalidSignerOrSignature();
        }

        if (roundState != 1) {
            revert RoundHasNotStarted();
        }

        uint len = _roundTokenIds[roundId].length;
        uint[] memory tokensToCombine = new uint[](_numToCombine);
        uint[] memory tokensToDeployer = new uint[](_numToDeployer);
        uint[] memory tokensToLowestIdOwner = new uint[](_numToLowestIdOwner);

        if (len != _numPerRound) {
            if (msg.sender == deployer) {
                roundState = 2;

                _returnAllTokens();

                emit RoundClosed(
                    roundId,
                    _signature,
                    _message,
                    address(0),
                    tokensToCombine,
                    tokensToDeployer,
                    tokensToLowestIdOwner
                );

                return;
            }
            revert RoundHasNotEnded(roundId);
        }

        roundState = 2;

        address winner = _chooseWinner(_signature, _message);

        // should not be possible to enter here,
        // because it's check in chooseWinner, but for safety
        if (winner == address(0)) {
            revert FailedToChooseWinnerTryAgain();
        }

        uint lowestTokenId = 999999;
        uint lowestTokenIdIndex;

        for (uint i; i < len; i = _uncheckedIncrement(i)) {
            uint currentTokenId = _roundTokenIds[roundId][i];

            if (currentTokenId < lowestTokenId) {
                lowestTokenId = currentTokenId;
                lowestTokenIdIndex = i;
            }

            if (i < _numToCombine) {
                if (lowestTokenIdIndex == i) {
                    tokensToCombine[i] = tokensToCombine[0];
                    tokensToCombine[0] = lowestTokenId;
                } else {
                    tokensToCombine[i] = currentTokenId;
                }
            } else if (i < _numToCombine + _numToDeployer) {
                uint idx = i - _numToCombine;

                if (lowestTokenIdIndex == i) {
                    tokensToDeployer[idx] = tokensToCombine[0];
                    tokensToCombine[0] = lowestTokenId;
                } else {
                    tokensToDeployer[idx] = currentTokenId;
                }
            } else {
                uint idx = i - _numToCombine - _numToDeployer;

                if (lowestTokenIdIndex == i) {
                    tokensToLowestIdOwner[idx] = tokensToCombine[0];
                    tokensToCombine[0] = lowestTokenId;
                } else {
                    tokensToLowestIdOwner[idx] = currentTokenId;
                }
            }
        }

        _combineAndTransferToWinner(tokensToCombine, winner);

        // transfer tokens to lowest id owner
        if (_numToLowestIdOwner > 0) {
            address lowestTokenIdOwner = _tokenOwners[lowestTokenId];

            _tryTransferTo(lowestTokenIdOwner, tokensToLowestIdOwner);
        }

        // transfer tokens to deployer
        if (_numToDeployer > 0) {
            _tryTransferTo(deployer, tokensToDeployer);
        }

        emit RoundClosed(
            roundId,
            _signature,
            _message,
            winner,
            tokensToCombine,
            tokensToDeployer,
            tokensToLowestIdOwner
        );
    }

    /**
     * @dev In case of a bug, the deployer can force close a round,
     * in which case all tokens are returned to their owners.
     * In case wgw.eth (0xA20C...5002) pause the whole game and there are
     * stuck tokens, then all tokens are return too.
     */
    function _returnAllTokens() private {
        uint len = _roundTokenIds[roundId].length;

        // return all tokens to their owners
        for (uint i; i < len; i = _uncheckedIncrement(i)) {
            uint tokenId = _roundTokenIds[roundId][i];
            nftContract.transferFrom(
                address(this),
                _tokenOwners[tokenId],
                tokenId
            );
        }
    }

    function _tryTransferTo(address to, uint[] memory tokens) private {
        uint len = tokens.length;

        for (uint i; i < len; i = _uncheckedIncrement(i)) {
            _tokenOwners[tokens[i]] = to;
            nftContract.transferFrom(address(this), to, tokens[i]);
            _tokenOwners[tokens[i]] = to; // ensurance?!
        }
    }

    /**
     * @dev Pass all tokens from `toCombine` to the nft's `combine` function.
     * Usually that method, accepts multiple tokens, and all but the first one are burned.
     * The first token is then transferred to the chosen winner.
     */
    function _combineAndTransferToWinner(
        uint[] memory toCombine,
        address winner
    ) private {
        nftContract.combine(toCombine);

        // update mappings
        _tokenOwners[toCombine[0]] = winner;
        uint len = toCombine.length;
        for (uint i = 1; i < len; i = _uncheckedIncrement(i)) {
            _tokenOwners[toCombine[i]] = address(0);
        }

        // transfer the combined token to the winner
        nftContract.transferFrom(address(this), winner, toCombine[0]);
    }

    /**
     * @dev Choose a winner, based on all round tokens.
     * The randomness is based on 2 seeds, all supplied player signature,
     * deployment seed, and a signature by the one that closed the round.
     */
    function _chooseWinner(
        bytes memory _signature,
        string calldata _message
    ) private returns (address) {
        bytes32 seed1 = _getHash(
            keccak256(abi.encode(_signature, _message, msg.sender, nonce))
        );
        uint num = _getRandomUint(1, _signatures.length - 1, seed1);
        bytes memory sig2 = _signatures[num];
        bytes32 seed2 = _getHash(keccak256(abi.encode(sig2)));

        uint winningTokenId = _getRandomUint(
            0,
            _roundTokenIds[roundId].length - 1,
            keccak256(abi.encode(seed1, sig2, nonce, seed2, _signature))
        );

        // if it's Zero, try again recursively until it's okay
        // that should not happen, but obviously it does, so..
        if (_tokenOwners[winningTokenId] == address(0)) {
            return _chooseWinner(_signature, _message);
        }

        return _tokenOwners[winningTokenId];
    }

    /**
     * @dev Basic Gas Optimization for loops
     */
    function _uncheckedIncrement(uint i) private pure returns (uint) {
        unchecked {
            return i + 1;
        }
    }

    function _getRandomUint(
        uint min,
        uint max,
        bytes32 seed
    ) internal returns (uint) {
        if (min > max) {
            revert MinGreaterThanMax();
        }

        uint range = max - min + 1;
        nonce = nonce + 1;

        uint randomNum = uint(
            keccak256(
                abi.encode(_signatures, nonce, _deployerSeedSig, seed, deployer)
            )
        );

        uint num = randomNum % range;

        return min + num;
    }

    function _getHash(bytes32 sig) private returns (bytes32) {
        nonce = nonce + 1;

        return
            keccak256(
                abi.encode(
                    sig,
                    _roundTokenIds[roundId],
                    roundId,
                    _signatures,
                    nonce,
                    msg.sender
                )
            );
    }

    function _recoverSigner(
        bytes memory _signature,
        string calldata _message
    ) public pure returns (address) {
        (bytes32 r, bytes32 s, uint8 v) = _splitSignature(_signature);

        bytes32 hash = keccak256(
            abi.encodePacked(
                "\x19Ethereum Signed Message:\n",
                _uintToString(bytes(_message).length),
                _message
            )
        );

        return ecrecover(hash, v, r, s);
    }

    /**
     * @dev Splits a signature into `r`, `s` and `v` parameters.
     */
    function _splitSignature(
        bytes memory sig
    ) private pure returns (bytes32 r, bytes32 s, uint8 v) {
        if (sig.length != 65) {
            revert InvalidSignature();
        }

        assembly {
            r := mload(add(sig, 0x20)) // first 32 bytes, after the length prefix
            s := mload(add(sig, 0x40)) // second 32 bytes, 64
            v := byte(0, mload(add(sig, 0x60))) // 96, final byte (first byte of the next 32 bytes)
        }
    }

    // See: OpenZeppelin Strings.toString
    function _uintToString(uint256 value) private pure returns (string memory) {
        bytes16 symbols = "0123456789abcdef";

        unchecked {
            uint256 length = _mathLog10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), symbols))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     * See: OpenZeppelin Math.log10
     */
    function _mathLog10(uint256 value) private pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }
}

// File: IUINTS721.sol



// SPDX-License-Identifier: MPL-2.0
pragma solidity ^0.8.18;

import "erc721a/contracts/IERC721A.sol";

interface IUINTS721 is IERC721A {
    function combine(uint[] memory tokenIds) external;
}
// File: erc721a/contracts/IERC721A.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

/**
 * @dev Interface of ERC721A.
 */
interface IERC721A {
    /**
     * The caller must own the token or be an approved operator.
     */
    error ApprovalCallerNotOwnerNorApproved();

    /**
     * The token does not exist.
     */
    error ApprovalQueryForNonexistentToken();

    /**
     * Cannot query the balance for the zero address.
     */
    error BalanceQueryForZeroAddress();

    /**
     * Cannot mint to the zero address.
     */
    error MintToZeroAddress();

    /**
     * The quantity of tokens minted must be more than zero.
     */
    error MintZeroQuantity();

    /**
     * The token does not exist.
     */
    error OwnerQueryForNonexistentToken();

    /**
     * The caller must own the token or be an approved operator.
     */
    error TransferCallerNotOwnerNorApproved();

    /**
     * The token must be owned by `from`.
     */
    error TransferFromIncorrectOwner();

    /**
     * Cannot safely transfer to a contract that does not implement the
     * ERC721Receiver interface.
     */
    error TransferToNonERC721ReceiverImplementer();

    /**
     * Cannot transfer to the zero address.
     */
    error TransferToZeroAddress();

    /**
     * The token does not exist.
     */
    error URIQueryForNonexistentToken();

    /**
     * The `quantity` minted with ERC2309 exceeds the safety limit.
     */
    error MintERC2309QuantityExceedsLimit();

    /**
     * The `extraData` cannot be set on an unintialized ownership slot.
     */
    error OwnershipNotInitializedForExtraData();

    // =============================================================
    //                            STRUCTS
    // =============================================================

    struct TokenOwnership {
        // The address of the owner.
        address addr;
        // Stores the start time of ownership with minimal overhead for tokenomics.
        uint64 startTimestamp;
        // Whether the token has been burned.
        bool burned;
        // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
        uint24 extraData;
    }

    // =============================================================
    //                         TOKEN COUNTERS
    // =============================================================

    /**
     * @dev Returns the total number of tokens in existence.
     * Burned tokens will reduce the count.
     * To get the total number of tokens minted, please see {_totalMinted}.
     */
    function totalSupply() external view returns (uint256);

    // =============================================================
    //                            IERC165
    // =============================================================

    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);

    // =============================================================
    //                            IERC721
    // =============================================================

    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables
     * (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in `owner`'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`,
     * checking first that contract recipients are aware of the ERC721 protocol
     * to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be have been allowed to move
     * this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement
     * {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId,
        bytes calldata data
    ) external payable;

    /**
     * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external payable;

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
     * whenever possible.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token
     * by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external payable;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the
     * zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external payable;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom}
     * for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool _approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);

    // =============================================================
    //                        IERC721Metadata
    // =============================================================

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

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

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);

    // =============================================================
    //                           IERC2309
    // =============================================================

    /**
     * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
     * (inclusive) is transferred from `from` to `to`, as defined in the
     * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
     *
     * See {_mintERC2309} for more details.
     */
    event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}