// File: contracts/token/Magic.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import '@solidstate/contracts/access/OwnableInternal.sol';
import '@solidstate/contracts/token/ERC20/ERC20.sol';

import './IMagic.sol';

contract Magic is IMagic, ERC20, OwnableInternal {
    uint256 public teamMintAmount;
    mapping(address => bool) private whitelist;

    function setWhitelist(address[] calldata minters) external onlyOwner {
        require(!whitelist[address(this)], 'Magic: whitelist already set');

        for (uint256 i; i < minters.length; i++) {
            whitelist[minters[i]] = true;
        }

        whitelist[address(this)] = true;
    }

    function mint(address account, uint256 amount) external override {
        require(whitelist[msg.sender], 'Magic: sender must be whitelisted');
        _mint(account, amount);
    }

    function teamMint(address account, uint256 amount) external onlyOwner {
        require(
            (totalSupply() + amount) / (teamMintAmount + amount) >= 10,
            'Magic: excessive mint'
        );
        _mint(account, amount);
        teamMintAmount += amount;
    }
}

// File: @solidstate/contracts/access/OwnableInternal.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {OwnableStorage} from './OwnableStorage.sol';

abstract contract OwnableInternal {
  using OwnableStorage for OwnableStorage.Layout;

  modifier onlyOwner {
    require(
      msg.sender == OwnableStorage.layout().owner,
      'Ownable: sender must be owner'
    );
    _;
  }
}

// File: @solidstate/contracts/token/ERC20/ERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {ERC20Base} from './base/ERC20Base.sol';
import {ERC20Extended} from './extended/ERC20Extended.sol';
import {ERC20Metadata} from './metadata/ERC20Metadata.sol';

/**
 * @title SolidState ERC20 implementation, including recommended extensions
 */
abstract contract ERC20 is ERC20Base, ERC20Extended, ERC20Metadata {}

// File: contracts/token/IMagic.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import '@solidstate/contracts/token/ERC20/IERC20.sol';

interface IMagic is IERC20 {
    function mint(address account, uint256 amount) external;
}

// File: @solidstate/contracts/access/OwnableStorage.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library OwnableStorage {
  struct Layout {
    address owner;
  }

  bytes32 internal constant STORAGE_SLOT = keccak256(
    'solidstate.contracts.storage.Ownable'
  );

  function layout () internal pure returns (Layout storage l) {
    bytes32 slot = STORAGE_SLOT;
    assembly { l.slot := slot }
  }

  function setOwner (
    Layout storage l,
    address owner
  ) internal {
    l.owner = owner;
  }
}

// File: @solidstate/contracts/token/ERC20/base/ERC20Base.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IERC20} from '../IERC20.sol';
import {ERC20BaseInternal} from './ERC20BaseInternal.sol';
import {ERC20BaseStorage} from './ERC20BaseStorage.sol';

/**
 * @title Base ERC20 implementation, excluding optional extensions
 */
abstract contract ERC20Base is IERC20, ERC20BaseInternal {
  /**
   * @inheritdoc IERC20
   */
  function totalSupply () override virtual public view returns (uint) {
    return _totalSupply();
  }

  /**
   * @inheritdoc IERC20
   */
  function balanceOf (
    address account
  ) override virtual public view returns (uint) {
    return _balanceOf(account);
  }

  /**
   * @inheritdoc IERC20
   */
  function allowance (
    address holder,
    address spender
  ) override virtual public view returns (uint) {
    return ERC20BaseStorage.layout().allowances[holder][spender];
  }

  /**
   * @inheritdoc IERC20
   */
  function approve (
    address spender,
    uint amount
  ) override virtual public returns (bool) {
    _approve(msg.sender, spender, amount);
    return true;
  }

  /**
   * @inheritdoc IERC20
   */
  function transfer (
    address recipient,
    uint amount
  ) override virtual public returns (bool) {
    _transfer(msg.sender, recipient, amount);
    return true;
  }

  /**
   * @inheritdoc IERC20
   */
  function transferFrom (
    address holder,
    address recipient,
    uint amount
  ) override virtual public returns (bool) {
    uint256 currentAllowance = ERC20BaseStorage.layout().allowances[holder][msg.sender];
    require(currentAllowance >= amount, 'ERC20: transfer amount exceeds allowance');
    unchecked {
      _approve(holder, msg.sender, currentAllowance - amount);
    }
    _transfer(holder, recipient, amount);
    return true;
  }
}

// File: @solidstate/contracts/token/ERC20/extended/ERC20Extended.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {ERC20Base, ERC20BaseStorage} from '../base/ERC20Base.sol';

/**
 * @title ERC20 safe approval extensions
 * @dev mitigations for transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
 */
abstract contract ERC20Extended is ERC20Base {
  /**
   * @notice increase spend amount granted to spender
   * @param spender address whose allowance to increase
   * @param amount quantity by which to increase allowance
   * @return success status (always true; otherwise function will revert)
   */
  function increaseAllowance (address spender, uint amount) virtual public returns (bool) {
    unchecked {
      mapping (address => uint) storage allowances = ERC20BaseStorage.layout().allowances[msg.sender];

      uint allowance = allowances[spender];
      require(allowance + amount >= allowance, 'ERC20Extended: excessive allowance');

      _approve(
        msg.sender,
        spender,
        allowances[spender] = allowance + amount
      );

      return true;
    }
  }

  /**
   * @notice decrease spend amount granted to spender
   * @param spender address whose allowance to decrease
   * @param amount quantity by which to decrease allowance
   * @return success status (always true; otherwise function will revert)
   */
  function decreaseAllowance (address spender, uint amount) virtual public returns (bool) {
    unchecked {
      mapping (address => uint) storage allowances = ERC20BaseStorage.layout().allowances[msg.sender];

      uint allowance = allowances[spender];
      require(amount <= allowance, 'ERC20Extended: insufficient allowance');

      _approve(
        msg.sender,
        spender,
        allowances[spender] = allowance - amount
      );

      return true;
    }
  }
}

// File: @solidstate/contracts/token/ERC20/metadata/ERC20Metadata.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {ERC20MetadataStorage} from './ERC20MetadataStorage.sol';
import {IERC20Metadata} from './IERC20Metadata.sol';

/**
 * @title ERC20 metadata extensions
 */
abstract contract ERC20Metadata is IERC20Metadata {
  /**
   * @inheritdoc IERC20Metadata
   */
  function name () virtual override public view returns (string memory) {
    return ERC20MetadataStorage.layout().name;
  }

  /**
   * @inheritdoc IERC20Metadata
   */
  function symbol () virtual override public view returns (string memory) {
    return ERC20MetadataStorage.layout().symbol;
  }

  /**
   * @inheritdoc IERC20Metadata
   */
  function decimals () virtual override public view returns (uint8) {
    return ERC20MetadataStorage.layout().decimals;
  }
}

// File: @solidstate/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IERC20Internal} from './IERC20Internal.sol';

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 */
interface IERC20 is IERC20Internal {
  /**
   * @notice query the total minted token supply
   * @return token supply
   */
  function totalSupply () external view returns (uint256);

  /**
   * @notice query the token balance of given account
   * @param account address to query
   * @return token balance
   */
  function balanceOf (
    address account
  ) external view returns (uint256);

  /**
   * @notice query the allowance granted from given holder to given spender
   * @param holder approver of allowance
   * @param spender recipient of allowance
   * @return token allowance
   */
  function allowance (
    address holder,
    address spender
  ) external view returns (uint256);

  /**
   * @notice grant approval to spender to spend tokens
   * @dev prefer ERC20Extended functions to avoid transaction-ordering vulnerability (see https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729)
   * @param spender recipient of allowance
   * @param amount quantity of tokens approved for spending
   * @return success status (always true; otherwise function should revert)
   */
  function approve (
    address spender,
    uint256 amount
  ) external returns (bool);

  /**
   * @notice transfer tokens to given recipient
   * @param recipient beneficiary of token transfer
   * @param amount quantity of tokens to transfer
   * @return success status (always true; otherwise function should revert)
   */
  function transfer (
    address recipient,
    uint256 amount
  ) external returns (bool);

  /**
   * @notice transfer tokens to given recipient on behalf of given holder
   * @param holder holder of tokens prior to transfer
   * @param recipient beneficiary of token transfer
   * @param amount quantity of tokens to transfer
   * @return success status (always true; otherwise function should revert)
   */
  function transferFrom (
    address holder,
    address recipient,
    uint256 amount
  ) external returns (bool);
}

// File: @solidstate/contracts/token/ERC20/base/ERC20BaseInternal.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IERC20Internal} from '../IERC20Internal.sol';
import {ERC20BaseStorage} from './ERC20BaseStorage.sol';

/**
 * @title Base ERC20 implementation, excluding optional extensions
 */
abstract contract ERC20BaseInternal is IERC20Internal {
  /**
   * @notice query the total minted token supply
   * @return token supply
   */
  function _totalSupply () virtual internal view returns (uint) {
    return ERC20BaseStorage.layout().totalSupply;
  }

  /**
   * @notice query the token balance of given account
   * @param account address to query
   * @return token balance
   */
  function _balanceOf (
    address account
  ) virtual internal view returns (uint) {
    return ERC20BaseStorage.layout().balances[account];
  }

  /**
   * @notice enable spender to spend tokens on behalf of holder
   * @param holder address on whose behalf tokens may be spent
   * @param spender recipient of allowance
   * @param amount quantity of tokens approved for spending
   */
  function _approve (
    address holder,
    address spender,
    uint amount
  ) virtual internal {
    require(holder != address(0), 'ERC20: approve from the zero address');
    require(spender != address(0), 'ERC20: approve to the zero address');

    ERC20BaseStorage.layout().allowances[holder][spender] = amount;

    emit Approval(holder, spender, amount);
  }

  /**
   * @notice mint tokens for given account
   * @param account recipient of minted tokens
   * @param amount quantity of tokens minted
   */
  function _mint (
    address account,
    uint amount
  ) virtual internal {
    require(account != address(0), 'ERC20: mint to the zero address');

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

    ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
    l.totalSupply += amount;
    l.balances[account] += amount;

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

  /**
   * @notice burn tokens held by given account
   * @param account holder of burned tokens
   * @param amount quantity of tokens burned
   */
  function _burn (
    address account,
    uint amount
  ) virtual internal {
    require(account != address(0), 'ERC20: burn from the zero address');

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

    ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
    uint256 balance = l.balances[account];
    require(balance >= amount, "ERC20: burn amount exceeds balance");
    unchecked {
      l.balances[account] = balance - amount;
    }
    l.totalSupply -= amount;

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

  /**
   * @notice transfer tokens from holder to recipient
   * @param holder owner of tokens to be transferred
   * @param recipient beneficiary of transfer
   * @param amount quantity of tokens transferred
   */
  function _transfer (
    address holder,
    address recipient,
    uint amount
  ) virtual internal {
    require(holder != address(0), 'ERC20: transfer from the zero address');
    require(recipient != address(0), 'ERC20: transfer to the zero address');

    _beforeTokenTransfer(holder, recipient, amount);

    ERC20BaseStorage.Layout storage l = ERC20BaseStorage.layout();
    uint256 holderBalance = l.balances[holder];
    require(holderBalance >= amount, 'ERC20: transfer amount exceeds balance');
    unchecked {
      l.balances[holder] = holderBalance - amount;
    }
    l.balances[recipient] += amount;

    emit Transfer(holder, recipient, amount);
  }

  /**
   * @notice ERC20 hook, called before all transfers including mint and burn
   * @dev function should be overridden and new implementation must call super
   * @param from sender of tokens
   * @param to receiver of tokens
   * @param amount quantity of tokens transferred
   */
  function _beforeTokenTransfer (
    address from,
    address to,
    uint amount
  ) virtual internal {}
}

// File: @solidstate/contracts/token/ERC20/base/ERC20BaseStorage.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library ERC20BaseStorage {
  struct Layout {
    mapping (address => uint) balances;
    mapping (address => mapping (address => uint)) allowances;
    uint totalSupply;
  }

  bytes32 internal constant STORAGE_SLOT = keccak256(
    'solidstate.contracts.storage.ERC20Base'
  );

  function layout () internal pure returns (Layout storage l) {
    bytes32 slot = STORAGE_SLOT;
    assembly { l.slot := slot }
  }
}

// File: @solidstate/contracts/token/ERC20/IERC20Internal.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Partial ERC20 interface needed by internal functions
 */
interface IERC20Internal {
  event Transfer(
    address indexed from,
    address indexed to,
    uint256 value
  );

  event Approval(
    address indexed owner,
    address indexed spender,
    uint256 value
  );
}

// File: @solidstate/contracts/token/ERC20/metadata/ERC20MetadataStorage.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

library ERC20MetadataStorage {
  struct Layout {
    string name;
    string symbol;
    uint8 decimals;
  }

  bytes32 internal constant STORAGE_SLOT = keccak256(
    'solidstate.contracts.storage.ERC20Metadata'
  );

  function layout () internal pure returns (Layout storage l) {
    bytes32 slot = STORAGE_SLOT;
    assembly { l.slot := slot }
  }

  function setName (
    Layout storage l,
    string memory name
  ) internal {
    l.name = name;
  }

  function setSymbol (
    Layout storage l,
    string memory symbol
  ) internal {
    l.symbol = symbol;
  }

  function setDecimals (
    Layout storage l,
    uint8 decimals
  ) internal {
    l.decimals = decimals;
  }
}

// File: @solidstate/contracts/token/ERC20/metadata/IERC20Metadata.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title ERC20 metadata interface
 */
interface IERC20Metadata {
  /**
   * @notice return token name
   * @return token name
   */
  function name () external view returns (string memory);

  /**
   * @notice return token symbol
   * @return token symbol
   */
  function symbol () external view returns (string memory);

  /**
   * @notice return token decimals, generally used only for display purposes
   * @return token decimals
   */
  function decimals () external view returns (uint8);
}