abstract contract ERC20Clone is Clone {
        /*///////////////////////////////////////////////////////////////
                                    EVENTS
        //////////////////////////////////////////////////////////////*/

        event Transfer(address indexed from, address indexed to, uint256 amount);

        event Approval(
            address indexed owner,
            address indexed spender,
            uint256 amount
        );

        /*///////////////////////////////////////////////////////////////
                                ERC20 STORAGE
        //////////////////////////////////////////////////////////////*/

        uint256 public totalSupply;

        mapping(address => uint256) public balanceOf;

        mapping(address => mapping(address => uint256)) public allowance;

        /*///////////////////////////////////////////////////////////////
                                METADATA
        //////////////////////////////////////////////////////////////*/

        function name() external pure returns (string memory) {
            return string(abi.encodePacked(_getArgUint256(0)));
        }

        function symbol() external pure returns (string memory) {
            return string(abi.encodePacked(_getArgUint256(0x20)));
        }

        function decimals() external pure returns (uint8) {
            return _getArgUint8(0x40);
        }

        /*///////////////////////////////////////////////////////////////
                                ERC20 LOGIC
        //////////////////////////////////////////////////////////////*/

        function approve(address spender, uint256 amount)
            public
            virtual
            returns (bool)
        {
            allowance[msg.sender][spender] = amount;

            emit Approval(msg.sender, spender, amount);

            return true;
        }

        function transfer(address to, uint256 amount)
            public
            virtual
            returns (bool)
        {
            balanceOf[msg.sender] -= amount;

            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] += amount;
            }

            emit Transfer(msg.sender, to, amount);

            return true;
        }

        function transferFrom(
            address from,
            address to,
            uint256 amount
        ) public virtual returns (bool) {
            uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

            if (allowed != type(uint256).max)
                allowance[from][msg.sender] = allowed - amount;

            balanceOf[from] -= amount;

            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] += amount;
            }

            emit Transfer(from, to, amount);

            return true;
        }

        /*///////////////////////////////////////////////////////////////
                        INTERNAL LOGIC
        //////////////////////////////////////////////////////////////*/

        function _mint(address to, uint256 amount) internal virtual {
            totalSupply += amount;

            // Cannot overflow because the sum of all user
            // balances can't exceed the max uint256 value.
            unchecked {
                balanceOf[to] += amount;
            }

            emit Transfer(address(0), to, amount);
        }

        function _burn(address from, uint256 amount) internal virtual {
            balanceOf[from] -= amount;

            // Cannot underflow because a user's balance
            // will never be larger than the total supply.
            unchecked {
                totalSupply -= amount;
            }

            emit Transfer(from, address(0), amount);
        }

        function _getImmutableVariablesOffset()
            internal
            pure
            returns (uint256 offset)
        {
            assembly {
                offset := sub(
                    calldatasize(),
                    add(shr(240, calldataload(sub(calldatasize(), 2))), 2)
                )
            }
        }
    }
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
function approve(address spender, uint256 amount)
    public
    virtual
    returns (bool)
{
    allowance[msg.sender][spender] = amount;

    emit Approval(msg.sender, spender, amount);

    return true;
}

/*///////////////////////////////////////////////////////////////
 ERC20 LOGIC
 //////////////////////////////////////////////////////////////*/