// File: wtfdeployer/WTFDeployerWithLotto.sol



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

// Created by @tunnckoCore / @wgw_eth / wgw.eth
// Github: https://github.com/tunnckocore/wtfdeployer-solidity
// BurnLotto: https://github.com/tunnckocore/burnlotto-solidity

import "./WTF20.sol";
import "burnlotto/contracts/BurnLotto.sol";

contract WTFDeployerWithLotto {
    // the deployer of the TokenDepoyer
    address public immutable deployer = msg.sender;

    // prettier-ignore
    bytes public SIGNATURE = bytes("0x8060c5c048f639d4726a858be8a3f57dd0d8c2fa87ecad46490ca7ebaf5de59964812750fdc6e2f9a6cbcd97b1d08ffdf488aa0e040e7cef8e7425d845462af11b");

    event ERC20TokenCreated(
        address deployedAt,
        string name,
        string symbol,
        uint256 supply,
        address creator
    );

    event BurnLottoCreated(address deployedAt, address token, bytes signature);

    function deployToken(
        string calldata name,
        string calldata symbol,
        uint256 initialSupply
    ) public returns (address) {
        return deployTokenCustom(name, symbol, initialSupply, 1234, false);
    }

    function deployTokenWithSalt(
        string calldata name,
        string calldata symbol,
        uint256 initialSupply,
        uint256 salt
    ) public returns (address) {
        return deployTokenCustom(name, symbol, initialSupply, salt, false);
    }

    function deployTokenCustom(
        string calldata name,
        string calldata symbol,
        uint256 initialSupply,
        uint256 salt,
        bool withLottery
    ) public returns (address) {
        WTF20 token = new WTF20{salt: bytes32(salt)}(name, symbol);

        uint256 supply = initialSupply * 10 ** WTF20(token).decimals();
        uint256 onePercent = supply / 100;

        // Mint 1% to the service, 99% to the caller (creator/deployer of this token)
        WTF20(token).mint(deployer, onePercent);
        WTF20(token).mint(msg.sender, supply - onePercent);

        // immediately renounce the ownership of the created token,
        // so that no one owns it, even this deployer contract, nor the caller/creator/minter
        WTF20(token).renounceOwnership();

        if (withLottery) {
            deployBurnLotto(address(token));
        }

        emit ERC20TokenCreated(
            address(token),
            name,
            symbol,
            supply,
            msg.sender
        );

        return address(token);
    }

    function deployTokenWithLottery(
        string calldata name,
        string calldata symbol,
        uint256 initialSupply
    ) public returns (address) {
        return deployTokenCustom(name, symbol, initialSupply, 5848, true);
    }

    function deployBurnLotto(address token) public returns (address) {
        return deployBurnLottoWithSignature(token, SIGNATURE);
    }

    function deployBurnLottoWithSignature(
        address token,
        bytes memory signature
    ) public returns (address) {
        BurnLotto lotto = new BurnLotto(token, signature);

        emit BurnLottoCreated(address(lotto), token, signature);

        return address(lotto);
    }
}
// File: burnlotto/contracts/BurnLotto.sol



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

// Created by @tunnckoCore / @wgw_eth / wgw.eth

interface IERC20Burnable {
    function burn(uint256 value) external;

    function balanceOf(address account) external view returns (uint256);

    function approve(address spender, uint256 amount) external returns (bool);

    function transfer(address to, uint256 amount) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}

error RoundOpened();
error RoundNotOpened();
error RoundPeriodTooLong();
error RoundPeriodTooShort();
error RoundTimeOver();
error RoundMaxEntriesExceeded();

contract BurnLotto {
    IERC20Burnable private _token;

    mapping(address => uint256) private depositsCount;
    mapping(address => uint256) private depositsAmount;
    mapping(uint256 => address) private roundWinner;
    address[] private allRoundWinners;
    address[] private depositors;

    bool private roundIsOpened;
    uint256 private roundId;
    uint256 private roundStartTime;
    uint256 private roundEndTime;

    bytes private deployerSignature;
    bytes[] private signaturesOfClosers;
    address private deployer = msg.sender;
    address private roundStarter;
    address private roundCloser;

    // PROD: uncomment when deploying
    event RoundStarted(
        address indexed creator,
        uint256 indexed roundStartTime,
        uint256 indexed period,
        uint256 roundId
    );

    // TEST: comment when deploying
    // event RoundStarted(
    //     address indexed creator,
    //     uint256 indexed period,
    //     uint256 roundId
    // );

    event RoundEntered(address indexed depositor, uint256 indexed amount);

    // PROD: uncomment when deploying
    event RoundEnded(
        address indexed winner,
        uint256 indexed tokensToWinner,
        uint256 indexed tokensToBurn,
        uint256 roundId,
        address roundCloser
    );

    // TEST: comment when deploying
    // event RoundEnded(
    //     uint256 indexed tokensToWinner,
    //     uint256 indexed tokensToBurn,
    //     uint256 roundId,
    //     address roundCloser
    // );

    constructor(address token, bytes memory signature) {
        _token = IERC20Burnable(token);
        deployerSignature = signature;
    }

    function startRound(uint256 period) external {
        if (roundIsOpened) {
            revert RoundOpened();
        }
        if (period > 10080) {
            revert RoundPeriodTooLong();
        }
        // PROD: uncomment when deploying
        if (period < 10) {
            revert RoundPeriodTooShort();
        }

        roundStartTime = block.timestamp;
        roundIsOpened = true;
        roundId += 1;
        roundStarter = msg.sender;

        // PROD: uncomment when deploying
        roundEndTime = roundStartTime + (period * 1 minutes);

        // TEST: comment when deploying
        // roundEndTime = roundStartTime + (period);

        // PROD: uncomment when deploying
        emit RoundStarted(roundStarter, roundStartTime, period, roundId);

        // TEST: comment when deploying
        // emit RoundStarted(roundStarter, period, roundId);
    }

    function enterRound(uint256 amount) external {
        if (!roundIsOpened) {
            revert RoundNotOpened();
        }
        if (block.timestamp > roundEndTime) {
            revert RoundTimeOver();
        }
        if (depositsCount[msg.sender] > 2) {
            revert RoundMaxEntriesExceeded();
        }

        depositors.push(msg.sender);
        depositsCount[msg.sender] += 1;
        depositsAmount[msg.sender] += amount;

        IERC20Burnable(_token).transferFrom(msg.sender, address(this), amount);

        emit RoundEntered(msg.sender, amount);
    }

    function endRound(
        uint256 percent,
        bytes memory signature
    ) external returns (address) {
        if (block.timestamp <= roundEndTime) {
            revert RoundOpened();
        }

        roundCloser = msg.sender;
        roundIsOpened = false;

        // Draw a winner randomly from the depositors array
        uint256 randomIdx = _getRandomUint(signature);
        address winner = depositors[randomIdx];

        allRoundWinners.push(winner);
        roundWinner[roundId] = winner;

        // Burn x% of the ERC-20 tokens that are deposited
        // the rest are transfered to the chosen winner
        uint256 deposited = IERC20Burnable(_token).balanceOf(address(this));
        uint256 tokensToBurn = (deposited / 100) * percent;
        uint256 tokensToWinner = deposited - tokensToBurn;

        // reset the state for the next round
        depositors = new address[](0);
        depositsCount[msg.sender] = 0;
        depositsAmount[msg.sender] = 0;

        IERC20Burnable(_token).burn(tokensToBurn);
        IERC20Burnable(_token).transfer(winner, tokensToWinner);

        // PROD: uncomment when deploying
        emit RoundEnded(
            winner,
            tokensToWinner,
            tokensToBurn,
            roundId,
            roundCloser
        );

        // TEST: comment when deploying
        // emit RoundEnded(tokensToWinner, tokensToBurn, roundId, roundCloser);

        return winner;
    }

    /**
     * View functions
     */

    function getTokenBalance() external view returns (uint256) {
        return IERC20Burnable(_token).balanceOf(address(this));
    }

    function getDepositors() external view returns (address[] memory) {
        return depositors;
    }

    function getDepositsCount(address player) external view returns (uint256) {
        return depositsCount[player];
    }

    function getDepositsAmount(address player) external view returns (uint256) {
        return depositsAmount[player];
    }

    function getRoundStartTime() external view returns (uint256) {
        return roundStartTime;
    }

    function getRoundEndTime() external view returns (uint256) {
        return roundEndTime;
    }

    function getRoundId() external view returns (uint256) {
        return roundId;
    }

    function getRoundWinner(uint256 _roundId) external view returns (address) {
        return roundWinner[_roundId];
    }

    function getAllWinners() external view returns (address[] memory) {
        return allRoundWinners;
    }

    function getRoundStarter() external view returns (address) {
        return roundStarter;
    }

    function getRoundCloser() external view returns (address) {
        return roundCloser;
    }

    function getDeployer() external view returns (address) {
        return deployer;
    }

    function isRoundStarted() external view returns (bool) {
        return roundIsOpened;
    }

    /**
     * Util functions
     */

    function _getRandomUint(
        bytes memory closerSignature
    ) internal view returns (uint) {
        uint randomNumber = uint(
            keccak256(
                abi.encode(
                    deployer,
                    closerSignature,
                    roundId,
                    msg.sender,
                    signaturesOfClosers,
                    allRoundWinners,
                    block.timestamp,
                    deployerSignature
                )
            )
        );

        return (randomNumber % depositors.length) - 1;
    }
}

// File: wtfdeployer/WTF20.sol



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

// Created by @tunnckoCore / @wgw_eth / wgw.eth
// Info: https://github.com/tunnckocore/wtfdeployer-solidity

// Compatible with ERC20 (OpenZeppelin impl), IERC20Metadata, ERC20Burnable, Ownable

// The reason to be custom copy, is this a bit cheaper to deploy and i'm short on funds.
// ERC20Burnable and Ownable are also implemented and included at the end of the contract.
// There's also a public mint function, available only for the owner of the contract.
// But this contract is intented through WTFDeployer,
// which automatically renounces the ownership on deployment,
// so that no one owns it, even that deployer contract.

contract WTF20 {
    address private _owner = msg.sender;

    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;
    string private _name;
    string private _symbol;

    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    function name() public view virtual returns (string memory) {
        return _name;
    }

    function symbol() public view virtual returns (string memory) {
        return _symbol;
    }

    function decimals() public view virtual returns (uint8) {
        return 18;
    }

    function totalSupply() public view virtual returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view virtual returns (uint256) {
        return _balances[account];
    }

    function transfer(
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        address owner_ = msg.sender;
        _transfer(owner_, to, amount);
        return true;
    }

    function allowance(
        address owner_,
        address spender
    ) public view virtual returns (uint256) {
        return _allowances[owner_][spender];
    }

    function approve(
        address spender,
        uint256 amount
    ) public virtual returns (bool) {
        address owner_ = msg.sender;
        _approve(owner_, spender, amount);
        return true;
    }

    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) public virtual returns (bool) {
        address spender = msg.sender;
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    function increaseAllowance(
        address spender,
        uint256 addedValue
    ) public virtual returns (bool) {
        address owner_ = msg.sender;
        _approve(owner_, spender, allowance(owner_, spender) + addedValue);
        return true;
    }

    function decreaseAllowance(
        address spender,
        uint256 subtractedValue
    ) public virtual returns (bool) {
        address owner_ = msg.sender;
        uint256 currentAllowance = allowance(owner_, spender);
        require(
            currentAllowance >= subtractedValue,
            "ERC20: decreased allowance below zero"
        );
        unchecked {
            _approve(owner_, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        // _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(
            fromBalance >= amount,
            "ERC20: transfer amount exceeds balance"
        );
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        // _afterTokenTransfer(from, to, amount);
    }

    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        // _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        // _afterTokenTransfer(address(0), account, amount);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        // _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        // _afterTokenTransfer(account, address(0), amount);
    }

    function _approve(
        address owner_,
        address spender,
        uint256 amount
    ) internal virtual {
        require(owner_ != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner_][spender] = amount;
        emit Approval(owner_, spender, amount);
    }

    function _spendAllowance(
        address owner_,
        address spender,
        uint256 amount
    ) internal virtual {
        uint256 currentAllowance = allowance(owner_, spender);
        if (currentAllowance != type(uint256).max) {
            require(
                currentAllowance >= amount,
                "ERC20: insufficient allowance"
            );
            unchecked {
                _approve(owner_, spender, currentAllowance - amount);
            }
        }
    }

    // ER20Burnable START

    function burn(uint256 amount) public virtual {
        _burn(msg.sender, amount);
    }

    function burnFrom(address account, uint256 amount) public virtual {
        _spendAllowance(account, msg.sender, amount);
        _burn(account, amount);
    }

    // ER20 Burnable END

    // Minimal Ownable

    function owner() public view virtual returns (address) {
        return _owner;
    }

    modifier onlyOwner() {
        require(owner() == msg.sender, "Ownable: caller is not the owner");
        _;
    }

    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }

    // OWNABLE END

    // Public mint function. It will not matter, because we immediately renounce ownership
    function mint(address account, uint256 amount) public onlyOwner {
        _mint(account, amount);
    }
}