contract ChadsToken is ERC20, Ownable, Pausable {
    using SafeMath for uint256;

    /// @notice uniswap listing rate
    uint256 public constant INITIAL_TOKENS_PER_ETH = 133_333 * 1 ether;

    /// @notice max burn percentage to teach virgins what happens when they sell too early
    uint256 public constant BURN_PCT = 50;

    /// @notice min burn percentage
    uint256 public constant MIN_BURN_PCT = 5;

    /// @notice reserve percentage of the burn percentage for rewarding true chads
    uint256 public constant BURN_RESERVE_PCT = 49;

    /// @notice WETH token address
    address public constant WETH = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

    /// @notice self-explanatory
    address public constant uniswapV2Factory = address(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);

    address public immutable burnReservesAddress;

    address public immutable initialDistributionAddress;    

    address public immutable teamAddress;

    address public immutable devAddress;

    address public immutable marketingAddress;

    TokenTimelock public burnReservesVault;

    TokenVesting public teamVault;

    TokenVesting public devVault;

    /// @notice liquidity sources (e.g. UniswapV2Router) 
    mapping(address => bool) public whitelistedSenders;

    /// @notice uniswap pair for CHADS/ETH
    address public uniswapPair;

    /// @notice Whether or not this token is first in uniswap CHADS<>ETH pair
    bool public isThisToken0;

    /// @notice last TWAP update time
    uint32 public blockTimestampLast;

    /// @notice last TWAP cumulative price
    uint256 public priceCumulativeLast;

    /// @notice last TWAP average price
    uint256 public priceAverageLast;

    /// @notice TWAP min delta (10-min)
    uint256 public minDeltaTwap;

    event TwapUpdated(uint256 priceCumulativeLast, uint256 blockTimestampLast, uint256 priceAverageLast);

    constructor(
        uint256 _minDeltaTwap,
        address _initialDistributionAddress,
        address _burnReservesAddress,
        address _teamAddress,
        address _devAddress,
        address _marketingAddress
    ) 
    public
    Ownable()
    ERC20("chads.vc", "CHADS")
    {
        setMinDeltaTwap(_minDeltaTwap);
        initialDistributionAddress = _initialDistributionAddress;
        burnReservesAddress = _burnReservesAddress;
        teamAddress = _teamAddress;
        devAddress = _devAddress;
        marketingAddress = _marketingAddress;
        _createBurnReservesVault(_burnReservesAddress);
        _distributeTokens(_initialDistributionAddress, _teamAddress, _devAddress, _marketingAddress);
        _initializePair();
        _pause();
    }

    modifier whenNotPausedOrInitialDistribution() {
        require(!paused() || msg.sender == initialDistributionAddress, "!paused && !initialDistributionAddress");
        _;
    }

    modifier onlyInitialDistributionAddress() {
        require(msg.sender == initialDistributionAddress, "!initialDistributionAddress");
        _;
    }

    /**
     * @dev Unpauses all transfers from the distribution address (initial liquidity pool).
     */
    function unpause() external virtual onlyInitialDistributionAddress {
        super._unpause();
    }

    /**
     * @dev Min time elapsed before twap is updated.
     */
    function setMinDeltaTwap(uint256 _minDeltaTwap) public onlyOwner {
        minDeltaTwap = _minDeltaTwap;
    }

    /**
     * @dev Initializes the TWAP cumulative values for the burn curve.
     */
    function initializeTwap() external onlyInitialDistributionAddress {
        require(blockTimestampLast == 0, "twap already initialized");
        (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);

        uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;
        
        blockTimestampLast = blockTimestamp;
        priceCumulativeLast = priceCumulative;
        priceAverageLast = INITIAL_TOKENS_PER_ETH;
    }

    /**
     * @dev Sets a whitelisted sender (liquidity sources mostly).
     */
    function setWhitelistedSender(address _address, bool _whitelisted) public onlyOwner {
        whitelistedSenders[_address] = _whitelisted;
    }

    function _distributeTokens(
        address _initialDistributionAddress,
        address _teamAddress,
        address _devAddress,
        address _marketingAddress
    ) 
    internal
    {
        // Initial Liquidity Pool (28M tokens) + Uniswap Pool (20M tokens)
        _mint(address(_initialDistributionAddress), 48 * 1e6 * 1e18);
        setWhitelistedSender(_initialDistributionAddress, true);

        // Team (3M tokens, 3 months lock + 6 months vesting)
        teamVault = new TokenVesting(_teamAddress, block.timestamp, 12 weeks, 24 weeks);
        setWhitelistedSender(address(teamVault), true);
        _mint(address(teamVault), 3 * 1e6 * 1e18);

        // Dapp Development (7M tokens, 2M unlocked, 5M months vesting)
        devVault = new TokenVesting(_devAddress, block.timestamp, 0, 24 weeks);
        setWhitelistedSender(address(devVault), true);
        _mint(_devAddress, 2 * 1e6 * 1e18);
        _mint(address(devVault), 5 * 1e6 * 1e18);

        // Marketing/ecosystem rewards (11M tokens)
        _mint(_marketingAddress, 11 * 1e6 * 1e18);
        setWhitelistedSender(_marketingAddress, true);
    }

    function _createBurnReservesVault(address _burnReservesAddress) internal {
        burnReservesVault = new TokenTimelock(_burnReservesAddress, block.timestamp + 2 weeks);
        setWhitelistedSender(address(burnReservesVault), true);
    }

    function _initializePair() internal {
        (address token0, address token1) = UniswapV2Library.sortTokens(address(this), address(WETH));
        isThisToken0 = (token0 == address(this));
        uniswapPair = UniswapV2Library.pairFor(uniswapV2Factory, token0, token1);
        setWhitelistedSender(uniswapPair, true);
    }

    function _isWhitelistedSender(address _sender) internal view returns (bool) {
        return whitelistedSenders[_sender];
    }    

    function _updateTwap() internal virtual returns (uint256) {
        (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);
        uint32 timeElapsed = blockTimestamp - blockTimestampLast; // overflow is desired

        if (timeElapsed > minDeltaTwap) {
            uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;

            // cumulative price is in (uq112x112 price * seconds) units so we simply wrap it after division by time elapsed
            FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(
                uint224((priceCumulative - priceCumulativeLast) / timeElapsed)
            );

            priceCumulativeLast = priceCumulative;
            blockTimestampLast = blockTimestamp;

            priceAverageLast = FixedPoint.decode144(FixedPoint.mul(priceAverage, 1 ether));

            emit TwapUpdated(priceCumulativeLast, blockTimestampLast, priceAverageLast);
        }

        return priceAverageLast;
    }

    function _transfer(address sender, address recipient, uint256 amount)
        internal
        virtual
        override
        whenNotPausedOrInitialDistribution()
    {
        if (!_isWhitelistedSender(sender)) {
            uint256 scaleFactor = 1e18;
            uint256 currentAmountOutPerEth = _updateTwap();
            uint256 currentBurnPct = BURN_PCT.mul(scaleFactor);
            if (currentAmountOutPerEth < INITIAL_TOKENS_PER_ETH) {
                // 50 / (INITIAL_TOKENS_PER_ETH / currentAmountOutPerEth)
                scaleFactor = 1e9;
                currentBurnPct = currentBurnPct.mul(scaleFactor)
                    .div(INITIAL_TOKENS_PER_ETH.mul(1e18)
                        .div(currentAmountOutPerEth));
                uint256 minBurnPct = MIN_BURN_PCT * scaleFactor / 10;
                currentBurnPct = currentBurnPct > minBurnPct ? currentBurnPct : minBurnPct;
            }

            uint256 totalBurnAmount = amount.mul(currentBurnPct).div(100).div(scaleFactor);
            uint256 burnReserveAmount = totalBurnAmount.mul(BURN_RESERVE_PCT).div(100);
            uint256 burnAmount = totalBurnAmount.sub(burnReserveAmount);
            
            super._transfer(sender, address(burnReservesVault), burnReserveAmount);
            super._burn(sender, burnAmount);

            amount = amount.sub(totalBurnAmount);
        }
        super._transfer(sender, recipient, amount);
    }

    function getCurrentTwap() public view returns (uint256) {
        (uint price0Cumulative, uint price1Cumulative, uint32 blockTimestamp) = 
            UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);
        uint32 timeElapsed = blockTimestamp - blockTimestampLast;

        uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;

        FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112(
            uint224((priceCumulative - priceCumulativeLast) / timeElapsed)
        );

        return FixedPoint.decode144(FixedPoint.mul(priceAverage, 1 ether));
    }

    function getLastTwap() public view returns (uint256) {
        return priceAverageLast;
    }
}
// @&#%&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @&@@%&@@&&&&%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@&@(....,*#&@@@&@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@&/..........*(%@@@&%#%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@&&%*..............,*(#%&@@&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@&@#......................*#&@@&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@&%@(..........................,(&@@&%&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@&&@(,.............................*/#@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@&@(,.................................*#&@@%&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@&&@#,....................................,/%@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@&@#,.......................................,*#&@&&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@&@(............................................,(&@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@/...............................................,/#@@@&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@&@(..................................................*#%@@&&@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@&@/......................................................,/&@&@@@@@@@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@%&@/.........................................................,/#%%%&@@@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@%@@(......................................,**/((((((((((((/*,....*(%&@@@@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@#@@*............................,,/%&&@@@@&#(/*,,,,,,,,,,/#&@@%((/*%@@#@@@@@@@@@@@@@@
// @@@@@@@@@@@@@@@&%@#.................,,/#%&@@@@@&%#/*,,,.,,,,,,,,,,,,,,,,,,,,.,,*(%&@@@@@&@@@@@@@@@@@
// @@@@@@@@@@@@@@@@@%@*.........*(#&@@@&%#/*,...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..*(&@@%@@@@@@@@@
// @@@@@@@@@@@@@@@@@&&@*....(&@@@%*,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%@&@@@@@@@@
// @@@@@@@@@@@@@@@@@@%@@@@@@@@(,,,,,,,,,,,.....,,,,...,,,,,,,,,,,,,,,,,,,,,,,,(&/.,,,,,,,,,,,*&&&@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@&&@(,,,,,*(%&@@@@@@@@@@@@@@&(,,,,,,,,,,,,,,,,,,,*%@(,,,,,,,,,,,,,,*#@@@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@&@(%@@@@@@&#/*.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,(@(,,,,,,,,,,,,,,,,*%&@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@&@/,,,,./%@@&%/,,,,,,,,,,,,,,,,,,,,,,,,,,.*(#@@(,,,,,,,,,,,,,,,,,,*%@@@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@&@/,,*&@@@%#######(*,,,,,,,,,,,,,,,,,,,,.(@%/*.,,,,,,,,,,,,,,,,,,,.(@%@@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@&@&*/%@@@@@%/%@@@@%(&&&@@&#,,,,,,,,,,,,,,.&@*,,,,,,,,,,,,,,,,,,,,,,.*%@&@@
// @@@@@@@@@@@@@@@@@@@@@@@@@&@%*.*%@&,,#@%//(##(#&/.*#@&*,,,,,,,,,,,,,,,.,,,,,,,,,,,,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@@@@@@@@@@%*,,,.*%&@@@@@@@@@@@@@@&(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@@@@@@%&@#,,,,,,,,,,**,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@@@@@&@%*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@@@&@&*.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,(%@@@@@&/,,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@#@%.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.*&@%/,,..,*%@#,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@&%@%,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,./@#.,#&%(,.,%@*,,,,,,,,,,,,(@&@@
// @@@@@@@@@@@@@@&&@%.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.*@&*/@(*&%*,#@*,,,,,,,,,,.*&&@@@
// @@@@@@@@@@@@@&@&*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*#%/.,*&%*,#@*,,,,,,,,,,*%&&@@@
// @@@@@@@@@@&&@%/,,,,,,,,(%&&%(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*/*,,,,,,,,*/*/&%*,#@*,,,,,,,,,,(@&@@@@
// @@@@@@@@@%&@(,,,,,,,,/&%*.,#@(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,(@@/,,,,,*&@@@#,,,#@*,,,,,,,,,(@@@@@@@
// @@@@@@@@&&%,.,,,,,,,,,,,,,,,(@&(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*#@%,,,,*(/.,,,./&&*,,,,,,,*%@@@@@@@@
// @@@@@@@@@&@@(*,,,,,,,*#&&&(,,...,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,(@%.,,,,,,/%&&@&/,,,,,,,*&@((@@@@@@
// @@@@@@@@@@@@%&@@@@@@@@&%&@@(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%@/.,,,,,,....,,,,,,,.(@%*.*%@@&&%
// @@@@@@@@@@@@@@@@@@@@@@@&@&/,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,&@(.,,,,,,,*,,,,,,,./#/,,.*%@@@@@
// @@@@@@@@@@@@@@@@@@@@&&@@@@&#*,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.(@#.,,,,,,/&%*,,,,,,,,,,,,.(@@@@@
// @@@@@@@@@@@@@@@@@%@@%*,,,,,*/,,(%(.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,%@#.,,,,,,,(@#,,,,,,,,,,,,.*%&@@@
// @@@@@@@@@@@@@@@@%@&,,,,,,,,,,,,,#@@/,,,,,,,,,,,,,,,,,,,,,,,,,,,.*(&@#.,,,,,,,./&#,,,,,,,,,,,,.*%&@@@
// @@@@@@@@@@@@@@@@#@&##&@@@@@@@@@@&&@%,,,,,,,,,,,,,,,,,,,,,,,,,*(&@%*,,,,,,,,,,./&#,,,,,,,,,,,,.*%&@@@
// @@@@@@@@@@@@@@@@@@%@@%*,,,,,,,,,,,(/,,,,,,,,,,,,,,,,,,,,,,*%@%/,,,,,,,,,,,,,,./&#,,,,,,,,,,,,.*%&&@@
// @@@@@@@@@@@@@@@@@@&@@@#*,..,,,,,,,,,,,,,,,,,,,,,,,,,,,,,/@&(,,,,,,,,,,,,,,,,,,/&#,,,,,,,,,,,,,,(&&@@
// @@@@@@@@@@@@@@@@@@@@&@@@&@@@@#,,,,,,,,,,,,,,,,,,,,,,,,,,*#/.,,,,,,,,,,,,,,,,,,#@(,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@@@@@&&@(.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*%@%*,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@@@@@&&&/.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,*(#&&#*.,,,,,,,,,,,,,,.(&&@@
// @@@@@@@@@@@@@@@@@&%@@&(,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*%@@@&(*,.,,,,,,,,,,,,,,,,,.(@%@@
// @@@@@@@@@@@@@@&@@%/,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*/#&@@@&#/*,,,,,,,,,,,,,,,,,,,,,,,,,*%&@@
// @@@@@@@@@@@@@%@&,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*/(%@@@%(*,.,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,*%&@@
// @@@@@@@@@@@@&@#,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,/%&@@@@&/*,,.,,,,,,,,,,,,,,,,*#/.,,,,,,,,,,,,,,,,*%&@@
// @@@@@@@@@@@@@@#,,,,,,,,,,,,,,,,,,,,,,,,,*#&@@&%%@@@@@(.,,,,,,,,,,,,,,,,,,,,(@(.,,,,,,,,,,,,,,,,*%@&@
// @@@@@@@@@@@@@%&@&%/,,,,,,,,,,,,,,,,,,*#@@&@@@@@@@@@@@%,,,,,,,,,,,,,,,,,,,,*%@/.,,,,,,,,,,,,,,,,.(&&@
// @@@@@@@@@@@@@@@@@&%@@%(///*,......*%@@&&@@@@@@@@@@@@&@&*,,,,,,,,,,,,,,,,,/@&/,,,,,,,,,,,,,,,,,,.(@&@
// @@@@@@@@@@@@@@@@@@@@@&&&&&&&&&&&&&&%@@@@@@@@@@@@@@@@@&&&(,,,,,,,,,,,,,,,(@%*,,,,,,,,,,,,,,,,,,,.*%&@
// @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%@%,,,,,,,,,,,,#@&/.,,,,,,,,,,,,,,,,,.*//(&&@
// $CHADS (chads.vc)
// @dev airblush@protonmail.com (virgin@protonmail.com was taken)
function initializeTwap() external onlyInitialDistributionAddress {
    require(blockTimestampLast == 0, "twap already initialized");
    (uint256 price0Cumulative, uint256 price1Cumulative, uint32 blockTimestamp) = 
        UniswapV2OracleLibrary.currentCumulativePrices(uniswapPair);

    uint256 priceCumulative = isThisToken0 ? price1Cumulative : price0Cumulative;
        
    blockTimestampLast = blockTimestamp;
    priceCumulativeLast = priceCumulative;
    priceAverageLast = INITIAL_TOKENS_PER_ETH;
}

/**
 * @dev Initializes the TWAP cumulative values for the burn curve.
 */