contract Vault is Whitelist, SpendLimit, ERC165 {
    event Received(address _from, uint _amount);
    event Transferred(address _to, address _asset, uint _amount);
    event BulkTransferred(address _to, address[] _assets);

    using SafeMath for uint256;

    /// @dev Supported ERC165 interface ID.
    bytes4 private constant _ERC165_INTERFACE_ID = 0x01ffc9a7; // solium-disable-line uppercase

    /// @dev ENS points to the ENS registry smart contract.
    ENS internal _ENS;
    /// @dev Is the registered ENS name of the oracle contract.
    bytes32 internal _oracleNode;

    /// @dev Constructor initializes the vault with an owner address and spend limit. It also sets up the oracle and controller contracts.
    /// @param _owner is the owner account of the wallet contract.
    /// @param _transferable indicates whether the contract ownership can be transferred.
    /// @param _ens is the ENS public registry contract address.
    /// @param _oracleName is the ENS name of the Oracle.
    /// @param _controllerName is the ENS name of the controller.
    /// @param _spendLimit is the initial spend limit.
    constructor(address _owner, bool _transferable, address _ens, bytes32 _oracleName, bytes32 _controllerName, uint _spendLimit) SpendLimit(_spendLimit) Ownable(_owner, _transferable) Controllable(_ens, _controllerName) public {
        _ENS = ENS(_ens);
        _oracleNode = _oracleName;
    }

    /// @dev Checks if the value is not zero.
    modifier isNotZero(uint _value) {
        require(_value != 0, "provided value cannot be zero");
        _;
    }

    /// @dev Ether can be deposited from any source, so this contract must be payable by anyone.
    function() public payable {
        //TODO question: Why is this check here, is it necessary or are we building into a corner?
        require(msg.data.length == 0, "data in fallback");
        emit Received(msg.sender, msg.value);
    }

    /// @dev Returns the amount of an asset owned by the contract.
    /// @param _asset address of an ERC20 token or 0x0 for ether.
    /// @return balance associated with the wallet address in wei.
    function balance(address _asset) external view returns (uint) {
        if (_asset != address(0)) {
            return ERC20(_asset).balanceOf(this);
        } else {
            return address(this).balance;
        }
    }

    /// @dev This is a bulk transfer convenience function, used to migrate contracts.
    /// If any of the transfers fail, this will revert.
    /// @param _to is the recipient's address, can't be the zero (0x0) address: transfer() will revert.
    /// @param _assets is an array of addresses of ERC20 tokens or 0x0 for ether.
    function bulkTransfer(address _to, address[] _assets) public onlyOwner {
        // check to make sure that _assets isn't empty
        require(_assets.length != 0, "asset array should be non-empty");
        // This loops through all of the transfers to be made
        for (uint i = 0; i < _assets.length; i++) {
            uint amount;
            // Get amount based on whether eth or erc20
            if (_assets[i] == address(0)) {
                amount = address(this).balance;
            } else {
                amount = ERC20(_assets[i]).balanceOf(address(this));
            }
            // use our safe, daily limit protected transfer
            transfer(_to, _assets[i], amount);
        }
        emit BulkTransferred(_to, _assets);
    }

    /// @dev Transfers the specified asset to the recipient's address.
    /// @param _to is the recipient's address.
    /// @param _asset is the address of an ERC20 token or 0x0 for ether.
    /// @param _amount is the amount of tokens to be transferred in base units.
    function transfer(address _to, address _asset, uint _amount) public onlyOwner isNotZero(_amount) {
        // Checks if the _to address is not the zero-address
        require(_to != address(0), "_to address cannot be set to 0x0");

        // If address is not whitelisted, take daily limit into account.
        if (!isWhitelisted[_to]) {
            // Update the available spend limit.
            _updateSpendAvailable();
            // Convert token amount to ether value.
            uint etherValue;
            bool tokenExists;
            if (_asset != address(0)) {
                (tokenExists, etherValue) = IOracle(PublicResolver(_ENS.resolver(_oracleNode)).addr(_oracleNode)).convert(_asset, _amount);
            } else {
                etherValue = _amount;
            }

            // If token is supported by our oracle or is ether
            // Check against the daily spent limit and update accordingly
            if (tokenExists || _asset == address(0)) {
                // Require that the value is under remaining limit.
                require(etherValue <= spendAvailable(), "transfer amount exceeds available spend limit");
                // Update the available limit.
                _setSpendAvailable(spendAvailable().sub(etherValue));
            }
        }
        // Transfer token or ether based on the provided address.
        if (_asset != address(0)) {
            require(ERC20(_asset).transfer(_to, _amount), "ERC20 token transfer was unsuccessful");
        } else {
            _to.transfer(_amount);
        }
        // Emit the transfer event.
        emit Transferred(_to, _asset, _amount);
    }

    /// @dev Checks for interface support based on ERC165.
    function supportsInterface(bytes4 interfaceID) external view returns (bool) {
        return interfaceID == _ERC165_INTERFACE_ID;
    }
}
//// @title Asset store with extra security features.
function transfer(address _to, address _asset, uint _amount) public onlyOwner isNotZero(_amount) {
    // Checks if the _to address is not the zero-address
    require(_to != address(0), "_to address cannot be set to 0x0");

    // If address is not whitelisted, take daily limit into account.
    if (!isWhitelisted[_to]) {
        // Update the available spend limit.
        _updateSpendAvailable();
        // Convert token amount to ether value.
        uint etherValue;
        bool tokenExists;
        if (_asset != address(0)) {
            (tokenExists, etherValue) = IOracle(PublicResolver(_ENS.resolver(_oracleNode)).addr(_oracleNode)).convert(_asset, _amount);
        } else {
            etherValue = _amount;
        }

        // If token is supported by our oracle or is ether
        // Check against the daily spent limit and update accordingly
        if (tokenExists || _asset == address(0)) {
            // Require that the value is under remaining limit.
            require(etherValue <= spendAvailable(), "transfer amount exceeds available spend limit");
            // Update the available limit.
            _setSpendAvailable(spendAvailable().sub(etherValue));
        }
    }
    // Transfer token or ether based on the provided address.
    if (_asset != address(0)) {
        require(ERC20(_asset).transfer(_to, _amount), "ERC20 token transfer was unsuccessful");
    } else {
        _to.transfer(_amount);
    }
    // Emit the transfer event.
    emit Transferred(_to, _asset, _amount);
}

/// @dev Transfers the specified asset to the recipient's address.
/// @param _to is the recipient's address.
/// @param _asset is the address of an ERC20 token or 0x0 for ether.
/// @param _amount is the amount of tokens to be transferred in base units.