// File: CanReclaimToken.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;


import "ERC20If.sol";
import "OwnableIf.sol";

/// @title CanReclaimToken
abstract contract CanReclaimToken is OwnableIf {

    function reclaimToken(ERC20If _token) external onlyOwner {
        uint256 balance = _token.balanceOf((address)(this));
        require(_token.transfer(_owner(), balance));
    }

}


// File: Claimable.sol



//SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;
pragma experimental SMTChecker;


import "Ownable.sol";

// File: openzeppelin-solidity/contracts/ownership/Claimable.sol

/**
 * @title Claimable
 * @dev Extension for the Ownable contract, where the ownership needs to be claimed.
 * This allows the new owner to accept the transfer.
 */
contract Claimable is Ownable {
    address public pendingOwner;

    /**
     * @dev Modifier throws if called by any account other than the pendingOwner.
     */
    modifier onlyPendingOwner() {
        require(msg.sender == pendingOwner, "no permission");
        _;
    }

    /**
     * @dev Allows the current owner to set the pendingOwner address.
     * @param newOwner The address to transfer ownership to.
     */
    function transferOwnership(address newOwner) override public onlyOwner {
        pendingOwner = newOwner;
    }

    /**
     * @dev Allows the pendingOwner address to finalize the transfer.
     */
    function claimOwnership() public onlyPendingOwner {
        emit OwnershipTransferred(owner, pendingOwner);
        owner = pendingOwner;
        pendingOwner = address(0);
    }
}


// File: ERC20If.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;

/// @title ERC20If
abstract contract ERC20If {
    function totalSupply() virtual public view returns (uint256);

    function balanceOf(address _who) virtual public view returns (uint256);

    function transfer(address _to, uint256 _value) virtual public returns (bool);

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

    function allowance(address _owner, address _spender) virtual public view returns (uint256);

    function transferFrom(address _from, address _to, uint256 _value) virtual public returns (bool);

    function approve(address _spender, uint256 _value) virtual public returns (bool);

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


// File: MemberMgr.sol



//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
pragma experimental SMTChecker;


import "MemberMgrIf.sol";
import "Claimable.sol";
import "CanReclaimToken.sol";

/// @title MemberMgr - add, delete, suspend and resume merchant and it’s eth address; reset the custodian’s eth address.
contract MemberMgr is Claimable, MemberMgrIf, CanReclaimToken {
    address public custodian;
    enum MerchantStatus {STOPPED, VALID}
    struct MerchantStatusData {
        MerchantStatus status;
        bool _exist;
    }

    function getStatusString(MerchantStatusData memory data) internal pure returns (string memory) {
        if (!data._exist) return "not-exist";
        if (data.status == MerchantStatus.STOPPED) {
            return "stopped";
        } else if (data.status == MerchantStatus.VALID) {
            return "valid";
        } else {
            return "not-exist";
        }
    }

    mapping(address => MerchantStatusData) public merchantStatus;
    address[] merchantList;

    function getMerchantNumber() public view returns (uint){
        return merchantList.length;
    }

    function getMerchantState(uint index) public view returns (address _addr, string memory _status){
        require(index < merchantList.length, "invalid index");
        address addr = merchantList[index];
        MerchantStatusData memory data = merchantStatus[addr];
        _addr = addr;
        _status = getStatusString(data);
    }

    function requireMerchant(address _who) override public view {
        MerchantStatusData memory merchantState = merchantStatus[_who];
        require (merchantState._exist, "not a merchant");

        require (merchantState.status != MerchantStatus.STOPPED, "merchant has been stopped");

        require(merchantState.status == MerchantStatus.VALID, "merchant not valid");
    }


    function requireCustodian(address _who) override public view {
        require(_who == custodian, "not custodian");
    }

    event CustodianSet(address indexed custodian);

    function setCustodian(address _custodian) external onlyOwner returns (bool) {
        require(_custodian != address(0), "invalid custodian address");
        custodian = _custodian;

        emit CustodianSet(_custodian);
        return true;
    }

    event NewMerchant(address indexed merchant);

    function addMerchant(address merchant) external onlyOwner returns (bool) {
        require(merchant != address(0), "invalid merchant address");
        MerchantStatusData memory data = merchantStatus[merchant];
        require(!data._exist, "merchant exists");
        merchantStatus[merchant] = MerchantStatusData({
            status : MerchantStatus.VALID,
            _exist : true
            });

        merchantList.push(merchant);
        emit NewMerchant(merchant);
        return true;
    }

    event MerchantStopped(address indexed merchant);

    function stopMerchant(address merchant) external onlyOwner returns (bool) {
        require(merchant != address(0), "invalid merchant address");
        MerchantStatusData memory data = merchantStatus[merchant];
        require(data._exist, "merchant not exists");
        require(data.status == MerchantStatus.VALID, "invalid status");
        merchantStatus[merchant].status = MerchantStatus.STOPPED;

        emit MerchantStopped(merchant);
        return true;
    }

    event MerchantResumed(address indexed merchant);

    function resumeMerchant(address merchant) external onlyOwner returns (bool) {
        require(merchant != address(0), "invalid merchant address");
        MerchantStatusData memory data = merchantStatus[merchant];
        require(data._exist, "merchant not exists");
        require(data.status == MerchantStatus.STOPPED, "invalid status");
        merchantStatus[merchant].status = MerchantStatus.VALID;

        emit MerchantResumed(merchant);
        return true;
    }

    function isCustodian(address addr) external view returns (bool) {
        return (addr == custodian);
    }

    function isMerchant(address addr) external view returns (bool) {
        return merchantStatus[addr]._exist && merchantStatus[addr].status == MerchantStatus.VALID;
    }
}

// File: MemberMgrIf.sol



pragma solidity ^0.7.0;
pragma experimental SMTChecker;

//SPDX-License-Identifier: MIT
/// @title MemberMgrIf
abstract contract MemberMgrIf {
    function requireMerchant(address _who) virtual public view;

    function requireCustodian(address _who) virtual public view;
}

// File: Ownable.sol



//SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;
pragma experimental SMTChecker;


import "OwnableIf.sol";

// File: openzeppelin-solidity/contracts/ownership/Ownable.sol

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
contract Ownable is OwnableIf {
    address public owner;

    function _owner() view override public returns (address){
        return owner;
    }

    //    event OwnershipRenounced(address indexed previousOwner);
    event OwnershipTransferred(
        address indexed previousOwner,
        address indexed newOwner
    );


    /**
     * @dev The Ownable constructor sets the original `owner` of the contract to the sender
     * account.
     */
    constructor() {
        owner = msg.sender;
    }

    //    /**
    //     * @dev Throws if called by any account other than the owner.
    //     */
    //    modifier onlyOwner() {
    //        require(msg.sender == owner);
    //        _;
    //    }

    /**
     * @dev Allows the current owner to relinquish control of the contract.
     * @notice Renouncing to ownership will leave the contract without an owner.
     * It will not be possible to call the functions with the `onlyOwner`
     * modifier anymore.
     */
    //   function renounceOwnership() public onlyOwner {
    //     emit OwnershipRenounced(owner);
    //     owner = address(0);
    //   }

    /**
     * @dev Allows the current owner to transfer control of the contract to a newOwner.
     * @param _newOwner The address to transfer ownership to.
     */
    function transferOwnership(address _newOwner) virtual public onlyOwner {
        _transferOwnership(_newOwner);
    }

    /**
     * @dev Transfers control of the contract to a newOwner.
     * @param _newOwner The address to transfer ownership to.
     */
    function _transferOwnership(address _newOwner) internal {
        require(_newOwner != address(0), "invalid _newOwner");
        emit OwnershipTransferred(owner, _newOwner);
        owner = _newOwner;
    }
}



// File: OwnableIf.sol



//SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;
pragma experimental SMTChecker;

// File: openzeppelin-solidity/contracts/ownership/Ownable.sol

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 */
abstract contract OwnableIf {

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(msg.sender == _owner(), "not owner......");
        _;
    }

    function _owner() view virtual public returns (address);
}