// File: BlackList.sol



pragma solidity ^0.4.0;

import "./Ownable.sol";

contract BlackList is Ownable{

    mapping (address => bool) internal isBlackListed;
    
    event DestroyedBlackFunds(address _blackListedUser, uint _balance);

    event AddedBlackList(address _user);

    event RemovedBlackList(address _user);
    
    function getBlackListStatus(address addr) public view returns (bool) {
        return isBlackListed[addr];
    }
    
    function addBlackList (address _evilUser) public  onlyAccessorBlackFunds(msg.sender) {
        isBlackListed[_evilUser] = true;
        emit AddedBlackList(_evilUser);
    }
    
    function removeBlackList (address _clearedUser) public onlyAccessorBlackFunds(msg.sender){
        isBlackListed[_clearedUser] = false;
        emit RemovedBlackList(_clearedUser);
    }
    
    modifier onlyAccessorBlackFunds(address addr){
        require(addr == blackListAccessorAddress, "You are not allowed!");
        _;
    }

}

// File: Erc20Token.sol



pragma solidity ^0.4.0;

import "./BlackList.sol";
import "./Erc20TokenInterface.sol";
import "./MathLibrary.sol";

contract Erc20Token is Erc20TokenInterface, BlackList {
    using MathLibrary for uint256;
    
    constructor() internal {
        balances[mintAddress] = 1500000000;
        totalSupply_ = 1500000000;
        emit Transfer(address(0), mintAddress, 1500000000);
        name = "IR Digital Token";
        symbol = "IRDT";
        decimals = 4;
    }

    /**
     * Transfer token from sender(caller) to '_to' account
     *
     * Requirements:
     *
     * - `_to` cannot be the zero address.
     * - the sender(caller) must have a balance of at least `_value`.
     */
    function transfer(address _to, uint256 _value) validAddress(_to, "_to address is not valid") smallerOrLessThan(_value, balances[msg.sender], "transfer value should be smaller than your balance") public returns (bool) {
        require(!isBlackListed[msg.sender], "from address is blacklisted");
        balances[msg.sender] = balances[msg.sender].sub(_value);
        balances[_to] = balances[_to].add(_value);
        emit Transfer(msg.sender, _to, _value);
        return true;
    }


    /**
     * sender(caller) transfer '_value' token to '_to' address from '_from' address
     *
     * Requirements:
     *
     * - `_to` and `_from` cannot be the zero address.
     * - `_from` must have a balance of at least `_value` .
     * - the sender(caller) must have allowance for `_from`'s tokens of at least `_value`.
     */
    function transferFrom(address _from, address _to, uint256 _value) validAddress(_from, "_from address is not valid") validAddress(_to, "_to address is not valid") public returns (bool) {
        require(_value<=allowances[_from][msg.sender], "_value should be smaller than your allowance");
        require(_value<=balances[_from],"_value should be smaller than _from's balance");
        require(!isBlackListed[_from], "from address is blacklisted");
        balances[_from] = balances[_from].sub(_value);
        balances[_to] = balances[_to].add(_value);
        allowances[_from][msg.sender] = allowances[_from][msg.sender].sub(_value);
        emit Transfer(_from, _to, _value);
        return true;
    }

    /**
     * change allowance of `_spender` to `_value` by sender(caller)
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address _spender, uint256 _value) validAddress(_spender, "_spender is not valid address") public returns (bool) {
        allowances[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value);
        return true;
    }

    /**
    * Atomically increases the allowance granted to `spender` by the sender(caller).
    * Emits an {Approval} event indicating the updated allowance.
    *
    * Requirements:
    *
    * - `spender` cannot be the zero address.
    */
    function increaseApproval(address _spender, uint _addedValue) validAddress(_spender, "_spender is not valid address") public returns (bool) {
        allowances[msg.sender][_spender] = allowances[msg.sender][_spender].add(_addedValue);
        emit Approval(msg.sender, _spender, allowances[msg.sender][_spender]);
        return true;
    }

    /**
    * Atomically decreases the allowance granted to `spender` by the sender(caller).
    * Emits an {Approval} event indicating the updated allowance.
    *
    * Requirements:
    *
    * - `_spender` cannot be the zero address.
    * - `_spender` must have allowance for the caller of at least `_subtractedValue`.
    */
    function decreaseApproval(address _spender, uint _subtractedValue) validAddress(_spender, "_spender is not valid address") public returns (bool) {
        uint oldValue = allowances[msg.sender][_spender];
        allowances[msg.sender][_spender] = _subtractedValue > oldValue ? 0 : oldValue.sub(_subtractedValue);
        emit Approval(msg.sender, _spender, allowances[msg.sender][_spender]);
        return true;
    }


    /**
    * Destroys `amount` tokens from `account`, reducing the
    * total supply.
    * Emits a {Transfer} event with `to` set to the zero address.
    *
    * Requirements:
    * - `amount` cannot be less than zero.
    * - `amount` cannot be more than sender(caller)'s balance.
    */
    function burn(uint256 amount) public {
        require(amount > 0, "amount cannot be less than zero");
        require(amount <= balances[msg.sender], "amount to burn is more than the caller's balance");
        balances[msg.sender] = balances[msg.sender].sub(amount);
        totalSupply_ = totalSupply_.sub(amount);
        emit Transfer(msg.sender, address(0), amount);
    }
    
    /**
    * sender(caller) create a 'value' token mint request.
    *
    * Requirement:
    * - sender(Caller) should be mintAccessorAddress
    */
    function mint(uint256 value) public {
        require(msg.sender == mintAccessorAddress,"you are not permitted to create mint request");
        totalSupply_ = totalSupply_.add(value);
        balances[mintAddress] = balances[mintAddress].add(value);
        emit Transfer(address(0), mintAddress, value);
    }
    
    /**
    * Destroys tokens from `blackUser`, if that account is blacklisted.
    * Emits a {DestroyedBlackFunds} event.
    * Requirements:
    * - `blackUser` should already be isBlackListed.
    */
    function destroyBlackFunds (address blackUser) public onlyAccessor(msg.sender) {
        require(isBlackListed[blackUser]);
        uint256 dirtyFunds = balances[blackUser];
        balances[blackUser] = 0;
        totalSupply_ = totalSupply_.sub(dirtyFunds);
        emit Transfer(blackUser, address(0), dirtyFunds);
        emit DestroyedBlackFunds(blackUser, dirtyFunds);
    }

    
    modifier onlyAccessor(address addr){
        require(addr == blackFundDestroyerAccessorAddress, "You are not allowed!");
        _;
    }
    
}


// File: Erc20TokenInterface.sol



pragma solidity ^0.4.0;


contract Erc20TokenInterface {
    mapping(address => uint256) internal balances;
    mapping(address => mapping(address => uint256)) internal allowances;
    uint256 internal totalSupply_;
    string public name;
    string public symbol;
    uint8 public decimals;

    modifier smallerOrLessThan(uint256 _value1, uint256 _value2, string errorMessage) {
        require(_value1 <= _value2, errorMessage);
        _;
    }

    modifier validAddress(address _address, string errorMessage) {
        require(_address != address(0), errorMessage);
        _;
    }

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

    function balanceOf(address _owner) public view returns (uint256) {
        return balances[_owner];
    }

    function allowance(address _owner, address _spender) public view returns (uint256) {
        return allowances[_owner][_spender];
    }

    function transfer(address to, uint256 value) public returns (bool);

    function transferFrom(address from, address to, uint256 value) public returns (bool);

    function approve(address spender, uint256 value) public returns (bool);

    function increaseApproval(address _spender, uint _addedValue) public returns (bool);

    function decreaseApproval(address _spender, uint _subtractedValue) public returns (bool);

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

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

}

// File: IRDT.sol



pragma solidity ^0.4.0;

import "./Erc20Token.sol";
import "./TokenContractWithTokenFee.sol";

/**
 * Website: IRDT.io
 **/
contract IRDT is TokenContractWithTokenFee {
    constructor (address[] BoDAddress, address[] accessors) public {
        BoDAddresses = BoDAddress;
        mintAccessorAddress = accessors[0];
        mintDestChangerAddress = accessors[1];
        blackListAccessorAddress = accessors[2];
        blackFundDestroyerAccessorAddress = accessors[3];
        mintAddress = accessors[4];
    }
}

// File: MathLibrary.sol



pragma solidity ^0.4.0;

library MathLibrary {
    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;
    }

}

// File: Ownable.sol



pragma solidity ^0.4.0;

import "./MathLibrary.sol";
import "./Erc20TokenInterface.sol";
contract Ownable {
    using MathLibrary for uint256;
    address public owner;
    address[] public BoDAddresses;
    
    address public mintAddress;
    
    address public mintDestChangerAddress;
    address public mintAccessorAddress;
    address public blackListAccessorAddress;
    address public blackFundDestroyerAccessorAddress;
    struct TransferObject {
        uint256 transferCounter;
        uint256 from;
        address to;
        mapping(address => bool) voted;
    }

    TransferObject transferObject;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
    event AuthorityTransfer(address indexed from, address indexed to);

    constructor() public payable {
        owner = msg.sender;
        
    }

     /**
    * change owner of contract
    */
    function transferOwnership(address newOwner) public onlyOwner {
        require(newOwner != address(0));
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }
    
     /**
    * change destination of mint address
    */
    function changeMintAddress(address addr) public{
        require(msg.sender == mintDestChangerAddress);
        mintAddress = addr;
    }
    
     /**
    * change accessor of mint destination changer
    */
    function changeMintDestChangerAddress(address addr) public{
        require(msg.sender == BoDAddresses[1]);
        mintDestChangerAddress = addr;
    }
    
     /**
    * change accessor of mint function
    */
    function changeMintAccessorAddress(address addr) public{
        require(msg.sender == BoDAddresses[0]);
        mintAccessorAddress = addr;
    }
    
     /**
    * change accessor of blackList
    */
    function changeBlackListAccessorAddress(address addr) public{
        require(msg.sender == BoDAddresses[2]);
        blackListAccessorAddress = addr;
    }
    
     /**
    * change accessor of blackList destroy fund
    */
    function changeBlackFundAccessorAddress(address addr) public{
        require(msg.sender == BoDAddresses[3]);
        blackFundDestroyerAccessorAddress = addr;
    }
    
     /**
    * sender(caller) vote for transfer `_from' address to '_to' address in board of directors
    *
    * Requirement:
    * - sender(Caller) and _from` should be in the board of directors.
    * - `_to` shouldn't be in the board of directors
    */
    function transferAuthority(uint256 from, address to) notInBoD(to, "_to address is already in board of directors") isAuthority(msg.sender, "you are not permitted to vote for transfer") public {
        require(from < BoDAddresses.length);
        if (BoDAddresses[from] == msg.sender) {
            transferAuth(from, to);
            return;
        }
        require(!transferObject.voted[msg.sender]);

        if (transferObject.from != from || transferObject.to != to) {
            transferObject.transferCounter = 0;
            for (uint j = 0; j < BoDAddresses.length; j++) {
                transferObject.voted[BoDAddresses[j]] = false;
            }
        }
        if (transferObject.transferCounter == 0) {
            transferObject.from = from;
            transferObject.to = to;
            
        }
        transferObject.transferCounter++;
        transferObject.voted[msg.sender] = true;
        if (transferObject.transferCounter == BoDAddresses.length - 1) {
            transferAuth(from, to);
        }
    }

     /**
    * this function is called if all of board of directors vote for the transfer `_from`->`_to'.
    */
    function transferAuth(uint256 from, address to) private {
        for (uint j = 0; j < BoDAddresses.length; j++) {
            transferObject.voted[BoDAddresses[j]] = false;
        }
        emit AuthorityTransfer(BoDAddresses[from], to);
        BoDAddresses[from] = to;
        transferObject.transferCounter = 0;
        
    }

     /**
    * This function is used by board of directors to remove other tokens in contract
    */
    function removeErc20TokensFromContract(address _token, address to) public isAuthority(msg.sender, "you are not permitted"){
        Erc20TokenInterface erc20Token = Erc20TokenInterface(_token);
        uint256 value = erc20Token.balanceOf(address(this));
        erc20Token.transfer(to,value);
    }
    
    
    modifier isAuthority(address authority, string errorMessage) {
        bool isBoD = false;
        for (uint i = 0; i < BoDAddresses.length; i++) {
            if (authority == BoDAddresses[i]) {
                isBoD = true;
                break;
            }
        }
        require(isBoD, errorMessage);
        _;
    }

    modifier notInBoD(address addr, string errorMessage){
        bool flag = true;
        for (uint i = 0; i < BoDAddresses.length; i++) {
            if (addr == BoDAddresses[i]) {
                flag = false;
                break;
            }
        }
        require(flag, errorMessage);
        _;
    }
    
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
    
}

// File: SignatureRecover.sol



pragma solidity ^0.4.0;

contract SignatureRecover {
    uint256 constant chainId = 1;

    struct Unit {
        address to;
        uint256 value;
        uint256 fee;
        uint256 nonce;
    }

    string private constant EIP712_DOMAIN = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
    string private constant UNIT_TYPE = "Unit(address to,uint256 value,uint256 fee,uint256 nonce)";

    bytes32 private constant EIP712_DOMAIN_TYPEHASH = keccak256(abi.encodePacked(EIP712_DOMAIN));
    bytes32 private constant UNIT_TYPEHASH = keccak256(abi.encodePacked(UNIT_TYPE));

    bytes32 private DOMAIN_SEPARATOR = keccak256(abi.encode(
            EIP712_DOMAIN_TYPEHASH,
            keccak256("IRDT"),
            keccak256("1"),
            chainId,
            this
        ));

    function hashUnit(Unit memory unitobj) private view returns (bytes32) {
        return keccak256(abi.encodePacked(
                "\x19\x01",
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(
                    UNIT_TYPEHASH,
                    unitobj.to,
                    unitobj.value,
                    unitobj.fee,
                    unitobj.nonce
                ))
            ));
    }


    /**
    * recover '_from' address by signature
    */
    function testVerify(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) internal view returns (address) {
        Unit memory _msgobj = Unit({
        to : _to,
        value : _value,
        fee : _fee,
        nonce : _nonce
        });
        return ecrecover(hashUnit(_msgobj), v, r, s);
    }
}

// File: TokenContractWithTokenFee.sol



pragma solidity ^0.4.0;

import "./Erc20Token.sol";
import "./SignatureRecover.sol";

contract TokenContractWithTokenFee is Erc20Token, SignatureRecover {

    mapping(bytes32 => bool) public signatures;

    event TransferPreSigned(address indexed from, address indexed to, address indexed delegate, uint256 amount, uint256 fee);

    modifier smallerOrLessThan(uint256 _value1, uint256 _value2, string errorMessage) {
        require(_value1 <= _value2, errorMessage);
        _;
    }

    modifier validAddress(address _address, string errorMessage) {
        require(_address != address(0), errorMessage);
        _;
    }

    /**
    * burn the specific signature from the signatures
    *
    * Requirement:
    * - sender(Caller) should be signer of that specific signature
    */
    function burnTransaction(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) validAddress(_to, "_to address is not valid") public {
        require(!signatures[s], "this signature is burned or done before");
        address from = testVerify(s, r, v, _to, _value, _fee, _nonce);
        require(from == msg.sender, "you're not permitted to burn this signature");
        signatures[s] = true;
    }

    /**
    * check if the transferPreSigned is valid or not!?
    *
    * Requirement:
    * - '_to' can not be zero address.
    */
    function validTransaction(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) validAddress(_to, "_to address is not valid") view public returns (bool, address) {
        address from = testVerify(s, r, v, _to, _value, _fee, _nonce);
        require(!isBlackListed[from], "from address is blacklisted");
        return (from != address(0) && !signatures[s] && balances[from] >= _value.add(_fee), from);
    }


    /**
    * submit the transferPreSigned
    *
    * Requirement:
    * - '_to' can not be zero address.
    * signature must be unused
    */
    function transferPreSigned(bytes32 s, bytes32 r, uint8 v, address _to, uint256 _value, uint256 _fee, uint256 _nonce) validAddress(_to, "_to address is not valid") public returns (bool){
        require(signatures[s] == false, "signature has been used");
        address from = testVerify(s, r, v, _to, _value, _fee, _nonce);
        require(from != address(0), "signature is wrong");
        require(!isBlackListed[from], "from address is blacklisted");
        balances[from] = balances[from].sub(_value.add(_fee));
        balances[_to] = balances[_to].add(_value);
        balances[msg.sender] = balances[msg.sender].add(_fee);
        signatures[s] = true;
        emit Transfer(from, _to, _value);
        emit Transfer(from, msg.sender, _fee);
        emit TransferPreSigned(from, _to, msg.sender, _value, _fee);
        return true;
    }

}