abstract contract BaseRfiToken is IERC20, IERC20Metadata, Ownable, AccessControl, Tokenomics {
    using SafeMath for uint256;
    using Address for address;

    mapping(address => uint256) internal _reflectedBalances;
    mapping(address => uint256) internal _balances;
    mapping(address => mapping(address => uint256)) internal _allowances;

    mapping(address => bool) internal _isExcludedFromFee;
    mapping(address => bool) internal _isExcludedFromRewards;
    address[] private _excluded;

    mapping(address => bool) internal _unlimitedSender;
    mapping(address => bool) internal _unlimitedRecipient;
    bytes32 public constant MULTISIG = keccak256("MULTISIG");


    constructor() {
        _reflectedBalances[owner()] = _reflectedSupply;

        // Exclude owner and this contract from fee
        _isExcludedFromFee[owner()] = true;
        _isExcludedFromFee[address(this)] = true;

        // Exclude the owner and this contract from rewards
        _exclude(owner());
        _exclude(address(this));

        // Set Owner to unlimited sender and recipient
        _unlimitedSender[owner()] = true;
        _unlimitedRecipient[owner()] = true;

        // Exclude marketing from fee and rewards
        _isExcludedFromFee[marketingAddress] = true;
        _exclude(address(marketingAddress));
        _unlimitedSender[marketingAddress] = true;
        _unlimitedRecipient[marketingAddress] = true;

        // Exclude presale from fee and rewards
        _isExcludedFromFee[presaleContract] = true;
        _exclude(address(presaleContract));
        _unlimitedSender[presaleContract] = true;
        _unlimitedRecipient[presaleContract] = true;

        // Exclude reserve wallet from fee and rewards
        _isExcludedFromFee[reserveWallet] = true;
        _exclude(address(reserveWallet));
        _unlimitedSender[reserveWallet] = true;
        _unlimitedRecipient[reserveWallet] = true;

        // Exclude pasvTimelock from fee and rewards
        _isExcludedFromFee[pasvTimelock] = true;
        _exclude(address(pasvTimelock));
        _unlimitedSender[pasvTimelock] = true;
        _unlimitedRecipient[pasvTimelock] = true;

        // Exclude Uniswap Router from fee and rewards
        _isExcludedFromFee[
        address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D)
        ] = true; // Uniswap router
        _exclude(address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D));
        
        _setupRole(MULTISIG, multisigWallet);


        // Mint tokens
        emit Transfer(address(0), owner(), TOTAL_SUPPLY);
    }

    /** Functions required by IERC20Metadata **/
    function name() external pure override returns (string memory) {
        return NAME;
    }

    function symbol() external pure override returns (string memory) {
        return SYMBOL;
    }

    function decimals() external pure override returns (uint8) {
        return DECIMALS;
    }

    /** Functions required by IERC20 **/
    function totalSupply() external pure override returns (uint256) {
        return TOTAL_SUPPLY;
    }

    function balanceOf(address account) public view override returns (uint256) {
        if (_isExcludedFromRewards[account]) return _balances[account];
        return tokenFromReflection(_reflectedBalances[account]);
    }

    function transfer(address recipient, uint256 amount)
    external
    override
    returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender)
    external
    view
    override
    returns (uint256)
    {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount)
    external
    override
    returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()].sub(
                amount,
                "ERC20: transfer amount exceeds allowance"
            )
        );
        return true;
    }

    /** Functions required by IERC20 - END **/

    function burn(uint256 amount) external {
        address sender = _msgSender();
        require(
            sender != address(0),
            "BaseRfiToken: burn from the zero address"
        );
        require(
            sender != address(burnAddress),
            "BaseRfiToken: burn from the burn address"
        );

        uint256 balance = balanceOf(sender);
        require(balance >= amount, "BaseRfiToken: burn amount exceeds balance");

        uint256 reflectedAmount = amount.mul(_getCurrentRate());

        _reflectedBalances[sender] = _reflectedBalances[sender].sub(
            reflectedAmount
        );
        if (_isExcludedFromRewards[sender])
            _balances[sender] = _balances[sender].sub(amount);

        _burnTokens(sender, amount, reflectedAmount);
    }

    function _burnTokens(
        address sender,
        uint256 tBurn,
        uint256 rBurn
    ) internal {
        _reflectedBalances[burnAddress] = _reflectedBalances[burnAddress].add(
            rBurn
        );
        if (_isExcludedFromRewards[burnAddress])
            _balances[burnAddress] = _balances[burnAddress].add(tBurn);

        emit Transfer(sender, burnAddress, tBurn);
    }

    function increaseAllowance(address spender, uint256 addedValue)
    public
    virtual
    returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].add(addedValue)
        );
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue)
    public
    virtual
    returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].sub(
                subtractedValue,
                "ERC20: decreased allowance below zero"
            )
        );
        return true;
    }

    function isExcludedFromReward(address account)
    external
    view
    returns (bool)
    {
        return _isExcludedFromRewards[account];
    }

    function reflectionFromToken(uint256 tAmount, bool deductTransferFee)
    external
    view
    returns (uint256)
    {
        require(tAmount <= TOTAL_SUPPLY, "Amount must be less than supply");
        if (!deductTransferFee) {
            (uint256 rAmount, , , , ) = _getValues(tAmount, 0);
            return rAmount;
        } else {
            (, uint256 rTransferAmount, , , ) = _getValues(
                tAmount,
                _getSumOfFees(_msgSender(), tAmount)
            );
            return rTransferAmount;
        }
    }

    function tokenFromReflection(uint256 rAmount)
    internal
    view
    returns (uint256)
    {
        require(
            rAmount <= _reflectedSupply,
            "Amount must be less than total reflections"
        );
        uint256 currentRate = _getCurrentRate();
        return rAmount.div(currentRate);
    }

    function excludeFromReward(address account) external {
        require(hasRole(MULTISIG, msg.sender), "Account must be multisig");
        require(!_isExcludedFromRewards[account], "Account is not included");
        _exclude(account);
    }

    function _exclude(address account) internal {
        if (_reflectedBalances[account] > 0) {
            _balances[account] = tokenFromReflection(
                _reflectedBalances[account]
            );
        }
        _isExcludedFromRewards[account] = true;
        _excluded.push(account);
    }

    function includeInReward(address account) external {
        require(hasRole(MULTISIG, msg.sender), "Account must be multisig");
        require(_isExcludedFromRewards[account], "Account is not excluded");
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_excluded[i] == account) {
                _excluded[i] = _excluded[_excluded.length - 1];
                _balances[account] = 0;
                _isExcludedFromRewards[account] = false;
                _excluded.pop();
                break;
            }
        }
    }

    function setExcludedFromFee(address account, bool value)
    external
    onlyOwner
    {
        _isExcludedFromFee[account] = value;
    }

    function isExcludedFromFee(address account) public view returns (bool) {
        return _isExcludedFromFee[account];
    }

    function setUnlimitedSender(address account, bool value)
    external
    {
        require(hasRole(MULTISIG, msg.sender), "Account must be multisig");
        _unlimitedSender[account] = value;
    }

    function setUnlimitedRecipient(address account, bool value)
    external
    {
        require(hasRole(MULTISIG, msg.sender), "Account must be multisig");
        _unlimitedRecipient[account] = value;
    }

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

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

    function _isUnlimitedSender(address account) internal view returns (bool) {
        return (_unlimitedSender[account] || account == owner());
    }

    function _isUnlimitedRecipient(address account)
    internal
    view
    returns (bool)
    {
        return (account == owner() ||
        account == burnAddress ||
        _unlimitedRecipient[account]);
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) private {
        require(
            sender != address(0),
            "BaseRfiToken: transfer from the zero address"
        );
        require(
            recipient != address(0),
            "BaseRfiToken: transfer to the zero address"
        );
        require(
            sender != address(burnAddress),
            "BaseRfiToken: transfer from the burn address"
        );
        require(amount > 0, "Transfer amount must be greater than zero");

        bool takeFee = true;

        /**
         * Check the amount is within the max allowed limit as long as a
         * unlimited sender/recepient is not involved in the transaction
         */
        if (
            amount > maxTransactionAmount &&
            !_isUnlimitedSender(sender) &&
            !_isUnlimitedRecipient(recipient)
        ) {
            revert("Transfer amount exceeds the maxTxAmount.");
        }

        if (_isExcludedFromFee[sender] || _isExcludedFromFee[recipient]) {
            takeFee = false;
        }

        _beforeTokenTransfer(sender, recipient, amount, takeFee);
        _transferTokens(sender, recipient, amount, takeFee);
    }

    function _transferTokens(
        address sender,
        address recipient,
        uint256 amount,
        bool takeFee
    ) private {
        uint256 sumOfFees = _getSumOfFees(sender, amount);
        if (!takeFee) {
            sumOfFees = 0;
        }

        (
        uint256 rAmount,
        uint256 rTransferAmount,
        uint256 tAmount,
        uint256 tTransferAmount,
        uint256 currentRate
        ) = _getValues(amount, sumOfFees);

        _reflectedBalances[sender] = _reflectedBalances[sender].sub(rAmount);
        _reflectedBalances[recipient] = _reflectedBalances[recipient].add(
            rTransferAmount
        );

        if (_isExcludedFromRewards[sender]) {
            _balances[sender] = _balances[sender].sub(tAmount);
        }
        if (_isExcludedFromRewards[recipient]) {
            _balances[recipient] = _balances[recipient].add(tTransferAmount);
        }

        _takeFees(amount, currentRate, sumOfFees);
        emit Transfer(sender, recipient, tTransferAmount);
    }

    function _takeFees(
        uint256 amount,
        uint256 currentRate,
        uint256 sumOfFees
    ) private {
        if (sumOfFees > 0) {
            _takeTransactionFees(amount, currentRate);
        }
    }

    function _getValues(uint256 tAmount, uint256 feesSum)
    internal
    view
    returns (
        uint256,
        uint256,
        uint256,
        uint256,
        uint256
    )
    {
        uint256 tTotalFees = tAmount.mul(feesSum).div(FEES_DIVISOR);
        uint256 tTransferAmount = tAmount.sub(tTotalFees);
        uint256 currentRate = _getCurrentRate();
        uint256 rAmount = tAmount.mul(currentRate);
        uint256 rTotalFees = tTotalFees.mul(currentRate);
        uint256 rTransferAmount = rAmount.sub(rTotalFees);

        return (
        rAmount,
        rTransferAmount,
        tAmount,
        tTransferAmount,
        currentRate
        );
    }

    function _getCurrentRate() internal view returns (uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply.div(tSupply);
    }

    function _getCurrentSupply() internal view returns (uint256, uint256) {
        uint256 rSupply = _reflectedSupply;
        uint256 tSupply = TOTAL_SUPPLY;

        for (uint256 i = 0; i < _excluded.length; i++) {
            if (
                _reflectedBalances[_excluded[i]] > rSupply ||
                _balances[_excluded[i]] > tSupply
            ) return (_reflectedSupply, TOTAL_SUPPLY);
            rSupply = rSupply.sub(_reflectedBalances[_excluded[i]]);
            tSupply = tSupply.sub(_balances[_excluded[i]]);
        }
        if (tSupply == 0 || rSupply < _reflectedSupply.div(TOTAL_SUPPLY))
            return (_reflectedSupply, TOTAL_SUPPLY);
        return (rSupply, tSupply);
    }

    function _beforeTokenTransfer(
        address sender,
        address recipient,
        uint256 amount,
        bool takeFee
    ) internal virtual;

    function _getSumOfFees(address sender, uint256 amount)
    internal
    view
    virtual
    returns (uint256);

    function _isV2Pair(address account) internal view virtual returns (bool);

    function _redistribute(
        uint256 amount,
        uint256 currentRate,
        uint256 fee,
        uint256 index
    ) internal {
        uint256 tFee = amount.mul(fee).div(FEES_DIVISOR);
        uint256 rFee = tFee.mul(currentRate);

        _reflectedSupply = _reflectedSupply.sub(rFee);
        _addFeeCollectedAmount(index, tFee);
    }

    function _takeTransactionFees(uint256 amount, uint256 currentRate)
    internal
    virtual;
}
function totalSupply() external pure override returns (uint256) {
    return TOTAL_SUPPLY;
}

/** Functions required by IERC20 **/