// File: /contracts/pLYS.sol





// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.7.5;

import "./types/ERC20.sol";
import "./types/Ownable.sol";
import "./types/Super.sol";

import "./libraries/SafeMath.sol";

contract PreElysianToken is Super {

  using SafeMath for uint256;

  bool public requireSellerApproval;
  bool public allowMinting;

  mapping( address => bool ) public isApprovedSeller;
  
  constructor(address _owner) Super( "Pre-Elysian", "pLYS", 18 ) Ownable(_owner) {
    uint256 initialSupply_ = 1000000000 * 1e18;
    requireSellerApproval = false;
    allowMinting = true;
    _addApprovedSeller( address(this) );
    _addApprovedSeller( _owner );
    _mint( _owner, initialSupply_ );
  }

  function allowOpenTrading() external onlyOwner() returns ( bool ) {
    requireSellerApproval = false;
    return requireSellerApproval;
  }

  function disableMinting() external onlyOwner() returns ( bool ) {
    allowMinting = false;
    MintingDisabled( msg.sender );
    return allowMinting;
  }

  function enableMinting() external onlyOwner() returns ( bool ) {
    allowMinting = true;
    MintingEnabled( msg.sender );
    return allowMinting;
  }

  function _addApprovedSeller( address approvedSeller_ ) internal {
    isApprovedSeller[approvedSeller_] = true;
  }

  function addApprovedSeller( address approvedSeller_ ) external onlyOwner() returns ( bool ) {
    _addApprovedSeller( approvedSeller_ );
    return isApprovedSeller[approvedSeller_];
  }

  function addApprovedSellers( address[] calldata approvedSellers_ ) external onlyOwner() returns ( bool ) {

    for( uint256 iteration_; approvedSellers_.length > iteration_; iteration_++ ) {
      _addApprovedSeller( approvedSellers_[iteration_] );
    }
    return true;
  }

  function _removeApprovedSeller( address disapprovedSeller_ ) internal {
    isApprovedSeller[disapprovedSeller_] = false;
  }

  function removeApprovedSeller( address disapprovedSeller_ ) external onlyOwner() returns ( bool ) {
    _removeApprovedSeller( disapprovedSeller_ );
    return isApprovedSeller[disapprovedSeller_];
  }

  function removeApprovedSellers( address[] calldata disapprovedSellers_ ) external onlyOwner() returns ( bool ) {
    for( uint256 iteration_; disapprovedSellers_.length > iteration_; iteration_++ ) {
      _removeApprovedSeller( disapprovedSellers_[iteration_] );
    }
    return true;
  }
  
  function _beforeTokenTransfer(address from_, address to_, uint256 amount_ ) internal override {
    require( (_balances[to_] > 0 || isApprovedSeller[from_] == true), "Account not approved to transfer pLYS." );
  }

  function mint( address recipient_, uint256 amount_) public virtual onlyOwner() {
    require( allowMinting, "Minting has been disabled." );
    _mint( recipient_, amount_ );
  }

   function burn(uint256 amount_) public virtual {
    _burn( msg.sender, amount_ );
  }

    function burnFrom( address account_, uint256 amount_ ) public virtual {
      _burnFrom( account_, amount_ );
    }

    function _burnFrom( address account_, uint256 amount_ ) internal virtual {
       uint256 decreasedAllowance_ = allowance( account_, msg.sender ).sub( amount_, "ERC20: burn amount exceeds allowance");
      _approve( account_, msg.sender, decreasedAllowance_ );
      _burn( account_, amount_ );
    }

    event MintingDisabled( address indexed operator );
    event MintingEnabled( address indexed operator );

}
// File: /contracts/types/Super.sol



pragma solidity 0.7.5;

import "./ERC20.sol";
import "./Ownable.sol";

abstract contract Super is ERC20, Ownable {

    constructor ( string memory name_, string memory symbol_, uint8 decimals_ ) ERC20( name_, symbol_, decimals_ ) {}
}
// File: /contracts/types/Ownable.sol




// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.7.5;

contract Ownable {
    address public owner;
    address public nominatedOwner;

    constructor(address _owner) public {
        require(_owner != address(0), "Owner address cannot be 0");
        owner = _owner;
        emit OwnerChanged(address(0), _owner);
    }

    function nominateNewOwner(address _owner) external onlyOwner {
        nominatedOwner = _owner;
        emit OwnerNominated(_owner);
    }

    function acceptOwnership() external {
        require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
        emit OwnerChanged(owner, nominatedOwner);
        owner = nominatedOwner;
        nominatedOwner = address(0);
    }

    modifier onlyOwner {
        _onlyOwner();
        _;
    }

    function _onlyOwner() private view {
        require(msg.sender == owner, "Only the contract owner may perform this action");
    }

    event OwnerNominated(address newOwner);
    event OwnerChanged(address oldOwner, address newOwner);
}

// File: /contracts/types/ERC20.sol





// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.7.5;


import "../interfaces/IERC20.sol";
import "../libraries/SafeMath.sol";

abstract contract ERC20 is IERC20 {

  using SafeMath for uint256;

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

  uint256 internal _totalSupply;

  string internal _name;
    
  string internal _symbol;
    
  uint8 internal _decimals;

  constructor (string memory name_, string memory symbol_, uint8 decimals_) {
    _name = name_;
    _symbol = symbol_;
    _decimals = decimals_;
  }

  function name() public view returns (string memory) {
    return _name;
  }

  function symbol() public view returns (string memory) {
    return _symbol;
  }

  function decimals() public view returns (uint8) {
    return _decimals;
  }

  function totalSupply() public view override returns (uint256) {
    return _totalSupply;
  }

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

  function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
    _transfer(msg.sender, recipient, amount);
    return true;
  }

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

    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(msg.sender, spender, amount);
        return true;
    }

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

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

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

  function _transfer(address sender, address recipient, uint256 amount) internal virtual {
    require(sender != address(0), "ERC20: transfer from the zero address");
    require(recipient != address(0), "ERC20: transfer to the zero address");

    _beforeTokenTransfer(sender, recipient, amount);

    _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
    _balances[recipient] = _balances[recipient].add(amount);
    emit Transfer(sender, recipient, amount);
  }

    function _mint(address account_, uint256 ammount_) internal virtual {
        require(account_ != address(0), "ERC20: mint to the zero address");
        _beforeTokenTransfer(address( this ), account_, ammount_);
        _totalSupply = _totalSupply.add(ammount_);
        _balances[account_] = _balances[account_].add(ammount_);
        emit Transfer(address( this ), account_, ammount_);
    }

    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

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

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

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

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

    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual { }
}


// File: /contracts/libraries/SafeMath.sol



// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.7.5;

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }

    function sqrrt(uint256 a) internal pure returns (uint c) {
        if (a > 3) {
            c = a;
            uint b = add( div( a, 2), 1 );
            while (b < c) {
                c = b;
                b = div( add( div( a, b ), b), 2 );
            }
        } else if (a != 0) {
            c = 1;
        }
    }

    function percentageAmount( uint256 total_, uint8 percentage_ ) internal pure returns ( uint256 percentAmount_ ) {
        return div( mul( total_, percentage_ ), 1000 );
    }

    function percentageOfTotal( uint256 part_, uint256 total_ ) internal pure returns ( uint256 percent_ ) {
        return div( mul(part_, 100) , total_ );
    }

    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow, so we distribute
        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
    }

    function substractPercentage( uint256 total_, uint8 percentageToSub_ ) internal pure returns ( uint256 result_ ) {
        return sub( total_, div( mul( total_, percentageToSub_ ), 1000 ) );
    }

    function quadraticPricing( uint256 payment_, uint256 multiplier_ ) internal pure returns (uint256) {
        return sqrrt( mul( multiplier_, payment_ ) );
    }

    function bondingCurve( uint256 supply_, uint256 multiplier_ ) internal pure returns (uint256) {
        return mul( multiplier_, supply_ );
    }
}
// File: /contracts/interfaces/IERC20.sol





// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity 0.7.5;

interface IERC20 {
  function totalSupply() external view returns (uint256);

  function balanceOf(address account) external view returns (uint256);

  function transfer(address recipient, uint256 amount) external returns (bool);

  function allowance(address owner, address spender) external view returns (uint256);

  function approve(address spender, uint256 amount) external returns (bool);

  function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);

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

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