pragma solidity ^0.8.20;




contract MemeCoin is ERC20, ReentrancyGuard, Ownable {

    /// @notice Event emitted when a user is blocked
    /// @param user The address of the blocked user
    error Blocked(address user);

    /// @notice Thrown when a user has insufficient balance
    error InsufficientBalance();

    /// @notice Thrown when a zero address is used
    error ZeroAddress();
    /// @notice Thrown when the new tax is out of range
    error InvalidTaxRange();
    /// @notice Thrown when the balance of the receiving wallet will exceed the max holding limit
    error MaxHoldingReached();

    /// @notice Emitted when tax is enabled
    event TaxEnabled();
    /// @notice Emitted when tax is disabled
    event TaxDisabled();
    /// @notice Emitted when the tax amount is updated
    /// @param newTax The new tax
    event TaxUpdated(uint256 newTax);
    /// @notice Emitted when the tax destination is updated
    /// @param newDestination the new address to receive the tax
    event TaxDestinationUpdated(address newDestination);
    /// @notice Emitted when an address is blocked
    /// @param user The address of the blocked account
    event AddressBlocked(address user);
    /// @notice Emitted when a user is unblocked
    /// @param user The address of the unblocked account
    event AddressUnblocked(address user);

    /// @notice Mapping of blocked addresses
    mapping (address user => bool isBlocked) public isBlocked;
    /// @notice Mapping of addresses that are not taxed when sending
    mapping (address user => bool notTaxedFrom) public notTaxedFrom;
    /// @notice Mapping of addresses that are not taxed when receiving
    mapping (address user => bool notTaxedTo) public notTaxedTo;
    /// @notice Mapping of addresses that are always taxed when sending
    mapping (address user => bool alwaysTaxedFrom) public alwaysTaxedFrom;
    /// @notice Mapping of addresses that are always taxed when receiving
    mapping (address user => bool alwaysTaxedTo) public alwaysTaxedTo;
    /// @notice Mapping of addresses that are exempt from the max holding limit
    mapping (address user => bool isExempt) public isMaxHoldingExempt;

    /// @notice Whether tax is enabled
    bool public taxEnabled;
    /// @notice The address to receive the taxes
    address public taxDestination;
    /// @notice The tax amount (e.g. 100 = 1%)
    uint256 public tax;
    /// @notice The minimum tax (e.g. 100 = 1%)
    uint256 public immutable minTax;
    /// @notice the maximum tax (e.g. 100 = 1%)
    uint256 public immutable maxTax;

    /// @notice The max amount a wallet can hold
    uint256 public immutable maxHoldingPerWallet;

    uint256 private constant TAX_PRECISION = 10_000;
    uint256 private constant HOLDING_PRECISION = 10_000;

    /// @param _name The name of the token
    /// @param _symbol The symbol of the token
    /// @param _taxEnabled Whether the tax is enabled
    /// @param _tax The tax amount (e.g. 100 = 1%)
    /// @param _minTax the minimum tax (e.g. 100 = 1%)
    /// @param _maxTax the maximum tax (e.g. 100 = 1%)
    /// @param _taxDestination The address to receive taxes
    /// @param maxHoldingPercent The percentage of the totalSupply that a wallet is allowed to hold (e.g. 100 = 1%)
    /// @param initialSupply The supply to be minted upon contract creation (will be multiplied by 10 ** 18)
    /// @param _owner The address of the contract owner
    constructor(
        string memory _name,
        string memory _symbol,
        bool _taxEnabled,
        uint256 _tax,
        uint256 _minTax,
        uint256 _maxTax,
        address _taxDestination,
        uint256 maxHoldingPercent,
        uint256 initialSupply,
        address _owner
    )
    ERC20(_name, _symbol)
    Ownable(_owner)
    {
        if (_minTax > _maxTax || _tax > _maxTax || _tax < _minTax || _minTax > TAX_PRECISION) {
            revert InvalidTaxRange();
        }
        taxEnabled = _taxEnabled;
        tax = _tax;
        minTax = _minTax;
        maxTax = _maxTax;
        _updateTaxDestination(_taxDestination);
        maxHoldingPerWallet = initialSupply * 1 ether * maxHoldingPercent / HOLDING_PRECISION;
        notTaxedFrom[_owner] = true;
        notTaxedTo[_owner] = true;
        isMaxHoldingExempt[_owner] = true;
        _mint(_owner, initialSupply * 1 ether);
    }

    function enableTax() external onlyOwner {
        taxEnabled = true;
        emit TaxEnabled();
    }

    function disableTax() external onlyOwner {
        taxEnabled = false;
        emit TaxDisabled();
    }

    function updateTax(uint256 newTax) external onlyOwner {
        if (newTax < minTax || newTax > maxTax) {
            revert InvalidTaxRange();
        }
        tax = newTax;
        emit TaxUpdated(newTax);
    }

    /// @notice Updates the tax destination to `newAddress`
    /// @param newDestination The new address to receive taxes
    function updateTaxDestination(address newDestination) external onlyOwner {
        _updateTaxDestination(newDestination);
    }

    /// @notice Set `exemptAddress` as exempt from the max holding limit
    /// @param exemptAddress The exempt address
    /// @param isExempt Whether the address is exempt from the max holding limit
    function setMaxHoldingExempt(address exemptAddress, bool isExempt) external onlyOwner {
        isMaxHoldingExempt[exemptAddress] = isExempt;
    }

    /// @notice add an address to the blocklist
    /// @dev "block" is a reserved symbol in Solidity, so we use "blockUser" instead
    /// @param _address The address to add to the blocklist
    function blockUser(address _address) external onlyOwner {
        if (_address == address(0)) {
            revert ZeroAddress();
        }
        isBlocked[_address] = true;
        emit AddressBlocked(_address);
    }

    /// @notice remove an address from the blocklist
    /// @param _address The address to remove from the blocklist
    function unblock(address _address) external onlyOwner {
        if (_address == address(0)) {
            revert ZeroAddress();
        }
        isBlocked[_address] = false;
        emit AddressUnblocked(_address);
    }

    /// @notice Set an address that is not taxed when sending
    /// @param _address User address
    /// @param _notTaxed Whether the user should be tax exempt when sending
    function setNotTaxedFrom(address _address, bool _notTaxed) external onlyOwner {
        notTaxedFrom[_address] = _notTaxed;
    }

    /// @notice Set an address that is not taxed when receiving
    /// @param _address User address
    /// @param _notTaxed Whether the address should be tax exempt when receiving
    function setNotTaxedTo(address _address, bool _notTaxed) external onlyOwner {
        notTaxedTo[_address] = _notTaxed;
    }

    /// @notice Set an address that is always taxed when sending
    /// @param _address User address
    /// @param _alwaysTaxed Whether the address should always be taxed when sending
    function setAlwaysTaxedFrom(address _address, bool _alwaysTaxed) external onlyOwner {
        alwaysTaxedFrom[_address] = _alwaysTaxed;
    }

    /// @notice Set an address that is always taxed when receiving
    /// @param _address User address
    /// @param _alwaysTaxed Whether the address should always be taxed when receiving
    function setAlwaysTaxedTo(address _address, bool _alwaysTaxed) external onlyOwner {
        alwaysTaxedTo[_address] = _alwaysTaxed;
    }

    function _updateTaxDestination(address newDestination) internal {
        if (newDestination == address(0)) revert ZeroAddress();
        taxDestination = newDestination;
        emit TaxDestinationUpdated(newDestination);
    }

    function _update(address from, address to, uint256 amount) internal virtual override {
        if (isBlocked[from]) revert Blocked(from);
        if (isBlocked[to]) revert Blocked(to);

        // check if the sender or receiver is not taxed
        if((!taxEnabled || notTaxedFrom[from] || notTaxedTo[to])
            && !alwaysTaxedFrom[from] && !alwaysTaxedTo[to]) {
            if (!isMaxHoldingExempt[to] && balanceOf(to) + amount > maxHoldingPerWallet) revert MaxHoldingReached();
            super._update(from, to, amount);
        } else {
            if (amount > balanceOf(from)) revert InsufficientBalance();
            if (!isMaxHoldingExempt[to] && balanceOf(to) + (amount * (TAX_PRECISION - tax) / TAX_PRECISION) > maxHoldingPerWallet) revert MaxHoldingReached();
            super._update(from, taxDestination, amount * tax / TAX_PRECISION);
            super._update(from, to, amount * (TAX_PRECISION - tax) / TAX_PRECISION);
        }
    }
}
[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"bool","name":"_taxEnabled","type":"bool"},{"internalType":"uint256","name":"_tax","type":"uint256"},{"internalType":"uint256","name":"_minTax","type":"uint256"},{"internalType":"uint256","name":"_maxTax","type":"uint256"},{"internalType":"address","name":"_taxDestination","type":"address"},{"internalType":"uint256","name":"maxHoldingPercent","type":"uint256"},{"internalType":"uint256","name":"initialSupply","type":"uint256"},{"internalType":"address","name":"_owner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"Blocked","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InvalidTaxRange","type":"error"},{"inputs":[],"name":"MaxHoldingReached","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"AddressBlocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"}],"name":"AddressUnblocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"newDestination","type":"address"}],"name":"TaxDestinationUpdated","type":"event"},{"anonymous":false,"inputs":[],"name":"TaxDisabled","type":"event"},{"anonymous":false,"inputs":[],"name":"TaxEnabled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newTax","type":"uint256"}],"name":"TaxUpdated","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":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"alwaysTaxedFrom","outputs":[{"internalType":"bool","name":"alwaysTaxedFrom","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"alwaysTaxedTo","outputs":[{"internalType":"bool","name":"alwaysTaxedTo","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"blockUser","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"disableTax","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"enableTax","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"isBlocked","outputs":[{"internalType":"bool","name":"isBlocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"isMaxHoldingExempt","outputs":[{"internalType":"bool","name":"isExempt","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxHoldingPerWallet","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTax","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minTax","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"notTaxedFrom","outputs":[{"internalType":"bool","name":"notTaxedFrom","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"notTaxedTo","outputs":[{"internalType":"bool","name":"notTaxedTo","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"bool","name":"_alwaysTaxed","type":"bool"}],"name":"setAlwaysTaxedFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"bool","name":"_alwaysTaxed","type":"bool"}],"name":"setAlwaysTaxedTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"exemptAddress","type":"address"},{"internalType":"bool","name":"isExempt","type":"bool"}],"name":"setMaxHoldingExempt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"bool","name":"_notTaxed","type":"bool"}],"name":"setNotTaxedFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"},{"internalType":"bool","name":"_notTaxed","type":"bool"}],"name":"setNotTaxedTo","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tax","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"taxDestination","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"taxEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"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":"value","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":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"unblock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"newTax","type":"uint256"}],"name":"updateTax","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newDestination","type":"address"}],"name":"updateTaxDestination","outputs":[],"stateMutability":"nonpayable","type":"function"}]