contract ERC20PermissionedMint is ERC20, ERC20Burnable, Owned {

    // Core
    address public timelock_address;

    // Minters
    address[] public minters_array; // Allowed to mint
    mapping(address => bool) public minters; // Mapping is also used for faster verification

    /* ========== CONSTRUCTOR ========== */

    constructor(
        address _creator_address,
        address _timelock_address,
        string memory _name,
        string memory _symbol
    ) 
    ERC20(_name, _symbol) 
    Owned(_creator_address)
    {
      timelock_address = _timelock_address;
    }


    /* ========== MODIFIERS ========== */

    modifier onlyByOwnGov() {
        require(msg.sender == timelock_address || msg.sender == owner, "Not owner or timelock");
        _;
    }

    modifier onlyMinters() {
       require(minters[msg.sender] == true, "Only minters");
        _;
    } 

    /* ========== RESTRICTED FUNCTIONS ========== */

    // Used by minters when user redeems
    function minter_burn_from(address b_address, uint256 b_amount) public onlyMinters {
        super.burnFrom(b_address, b_amount);
        emit TokenMinterBurned(b_address, msg.sender, b_amount);
    }

    // This function is what other minters will call to mint new tokens 
    function minter_mint(address m_address, uint256 m_amount) public onlyMinters {
        super._mint(m_address, m_amount);
        emit TokenMinterMinted(msg.sender, m_address, m_amount);
    }

    // Adds whitelisted minters 
    function addMinter(address minter_address) public onlyByOwnGov {
        require(minter_address != address(0), "Zero address detected");

        require(minters[minter_address] == false, "Address already exists");
        minters[minter_address] = true; 
        minters_array.push(minter_address);

        emit MinterAdded(minter_address);
    }

    // Remove a minter 
    function removeMinter(address minter_address) public onlyByOwnGov {
        require(minter_address != address(0), "Zero address detected");
        require(minters[minter_address] == true, "Address nonexistant");
        
        // Delete from the mapping
        delete minters[minter_address];

        // 'Delete' from the array by setting the address to 0x0
        for (uint i = 0; i < minters_array.length; i++){ 
            if (minters_array[i] == minter_address) {
                minters_array[i] = address(0); // This will leave a null in the array and keep the indices the same
                break;
            }
        }

        emit MinterRemoved(minter_address);
    }

    /* ========== EVENTS ========== */
    
    event TokenMinterBurned(address indexed from, address indexed to, uint256 amount);
    event TokenMinterMinted(address indexed from, address indexed to, uint256 amount);
    event MinterAdded(address minter_address);
    event MinterRemoved(address minter_address);
}
function minter_burn_from(address b_address, uint256 b_amount) public onlyMinters {
    super.burnFrom(b_address, b_amount);
    emit TokenMinterBurned(b_address, msg.sender, b_amount);
}

// Used by minters when user redeems