contract Wallet is Vault {

    using Address for address;

    event SetTopUpLimit(address _sender, uint _amount);
    event SubmittedTopUpLimitChange(uint _amount);
    event CancelledTopUpLimitChange(address _sender, uint _amount);

    event ToppedUpGas(address _sender, address _owner, uint _amount);

    event ExecutedTransaction(address _destination, uint _value, bytes _data);

    using SafeMath for uint256;

    uint constant private MINIMUM_TOPUP_LIMIT = 1 finney; // solium-disable-line uppercase
    uint constant private MAXIMUM_TOPUP_LIMIT = 500 finney; // solium-disable-line uppercase

    /// @dev these are needed to allow for the executeTransaction method
    uint32 private constant _TRANSFER= 0xa9059cbb;
    uint32 private constant _APPROVE = 0x095ea7b3;

    uint public topUpLimit;
    uint private _topUpLimitDay;
    uint private _topUpAvailable;

    uint public pendingTopUpLimit;
    bool public submittedTopUpLimit;
    bool public initializedTopUpLimit;

    /// @dev Constructor initializes the wallet top up limit and the vault contract.
    /// @param _owner is the owner account of the wallet contract.
    /// @param _transferable indicates whether the contract ownership can be transferred.
    /// @param _ens is the address of the ENS.
    /// @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) Vault(_owner, _transferable, _ens, _oracleName, _controllerName, _spendLimit) public {
        _topUpLimitDay = now;
        topUpLimit = MAXIMUM_TOPUP_LIMIT;
        _topUpAvailable = topUpLimit;
    }

    /// @dev Returns the available daily gas top up balance - accounts for daily limit reset.
    /// @return amount of gas in wei.
    function topUpAvailable() external view returns (uint) {
        if (now > _topUpLimitDay + 24 hours) {
            return topUpLimit;
        } else {
            return _topUpAvailable;
        }
    }

    /// @dev Initialize a daily gas top up limit.
    /// @param _amount is the gas top up amount in wei.
    function initializeTopUpLimit(uint _amount) external onlyOwner {
        // Require that the top up limit has not been initialized.
        require(!initializedTopUpLimit, "top up limit has already been initialized");
        // Require that the limit amount is within the acceptable range.
        require(MINIMUM_TOPUP_LIMIT <= _amount && _amount <= MAXIMUM_TOPUP_LIMIT, "top up amount is outside of the min/max range");
        // Modify spend limit based on the provided value.
        _modifyTopUpLimit(_amount);
        // Flag operation as initialized.
        initializedTopUpLimit = true;
        // Emit the set limit event.
        emit SetTopUpLimit(msg.sender, _amount);
    }

    /// @dev Set a daily top up top up limit.
    /// @param _amount is the daily top up limit amount in wei.
    function submitTopUpLimit(uint _amount) external onlyOwner {
        // Require that the top up limit has been initialized.
        require(initializedTopUpLimit, "top up limit has not been initialized");
        // Require that the limit amount is within the acceptable range.
        require(MINIMUM_TOPUP_LIMIT <= _amount && _amount <= MAXIMUM_TOPUP_LIMIT, "top up amount is outside of the min/max range");
        // Assign the provided amount to pending daily limit change.
        pendingTopUpLimit = _amount;
        // Flag the operation as submitted.
        submittedTopUpLimit = true;
        // Emit the submission event.
        emit SubmittedTopUpLimitChange(_amount);
    }

    /// @dev Confirm pending set top up limit operation.
    function confirmTopUpLimit(uint _amount) external onlyController {
        // Require that the operation has been submitted.
        require(submittedTopUpLimit, "top up limit has not been submitted");
        // Assert that the pending top up limit amount is within the acceptable range.
        require(MINIMUM_TOPUP_LIMIT <= pendingTopUpLimit && pendingTopUpLimit <= MAXIMUM_TOPUP_LIMIT, "top up amount is outside the min/max range");
        // Assert that confirmed and pending topup limit are the same.
        require(_amount == pendingTopUpLimit, "confirmed and pending topup limit are not same");
        // Modify top up limit based on the pending value.
        _modifyTopUpLimit(pendingTopUpLimit);
        // Emit the set limit event.
        emit SetTopUpLimit(msg.sender, pendingTopUpLimit);
        // Reset pending daily limit.
        pendingTopUpLimit = 0;
        // Reset the submission flag.
        submittedTopUpLimit = false;
    }

    /// @dev Cancel pending set top up limit operation.
    function cancelTopUpLimit(uint _amount) external onlyController {
        // Make sure a topup limit update has been submitted
        require(submittedTopUpLimit, "a topup limit has to be submitted");
        // Require that pending and confirmed spend limit are the same
        require(pendingTopUpLimit == _amount, "pending and cancelled top up limits dont match");
        // Reset pending daily limit.
        pendingTopUpLimit = 0;
        // Reset the submitted operation flag.
        submittedTopUpLimit = false;
        // Emit the cancellation event.
        emit CancelledTopUpLimitChange(msg.sender, _amount);
    }

    /// @dev Refill owner's gas balance.
    /// @dev Revert if the transaction amount is too large
    /// @param _amount is the amount of ether to transfer to the owner account in wei.
    function topUpGas(uint _amount) external isNotZero(_amount) {
        // Require that the sender is either the owner or a controller.
        require(_isOwner() || _isController(msg.sender), "sender is neither an owner nor a controller");
        // Account for the top up limit daily reset.
        _updateTopUpAvailable();
        // Make sure the available top up amount is not zero.
        require(_topUpAvailable != 0, "available top up limit cannot be zero");
        // Fail if there isn't enough in the daily top up limit to perform topUp
        require(_amount <= _topUpAvailable, "available top up limit less than amount passed in");
        // Reduce the top up amount from available balance and transfer corresponding
        // ether to the owner's account.
        _topUpAvailable = _topUpAvailable.sub(_amount);
        owner().transfer(_amount);
        // Emit the gas top up event.
        emit ToppedUpGas(tx.origin, owner(), _amount);
    }

    /// @dev Modify the top up limit and top up available based on the provided value.
    /// @dev _amount is the daily limit amount in wei.
    function _modifyTopUpLimit(uint _amount) private {
        // Account for the top up limit daily reset.
        _updateTopUpAvailable();
        // Set the daily limit to the provided amount.
        topUpLimit = _amount;
        // Lower the available limit if it's higher than the new daily limit.
        if (_topUpAvailable > topUpLimit) {
            _topUpAvailable = topUpLimit;
        }
    }

    /// @dev Update available top up limit based on the daily reset.
    function _updateTopUpAvailable() private {
        if (now > _topUpLimitDay.add(24 hours)) {
            // Advance the current day by how many days have passed.
            uint extraDays = now.sub(_topUpLimitDay).div(24 hours);
            _topUpLimitDay = _topUpLimitDay.add(extraDays.mul(24 hours));
            // Set the available limit to the current top up limit.
            _topUpAvailable = topUpLimit;
        }
    }

    /// @dev This function allows for the owner to send transaction from the Wallet to arbitrary contracts
    /// @dev This function does not allow for the sending of data to arbitrary (non-contract) addresses
    /// @param _destination address of the transaction
    /// @param _value ETH amount in wei
    /// @param _data transaction payload binary
    function executeTransaction(address _destination, uint _value, bytes _data, bool _destinationIsContract) external onlyOwner {

        // Check if the Address is a Contract
        // This should avoid users accidentally sending Value and Data to a plain old address
        if (_destinationIsContract) {
            require(address(_destination).isContract(), "executeTransaction for a contract: call to non-contract");
        } else {
            require(!address(_destination).isContract(), "executeTransaction for a non-contract: call to contract");
        }

        // Check if there exists at least a method signature in the transaction payload
        if (_data.length >= 4) {
            // Get method signature
            uint32 signature = bytesToUint32(_data, 0);

            // Check if method is either ERC20 transfer or approve
            if (signature == _TRANSFER || signature == _APPROVE) {
                require(_data.length >= 4 + 32 + 32, "invalid transfer / approve transaction data");
                uint amount = sliceUint(_data, 4 + 32);
                // The "toOrSpender" is the '_to' address for a ERC20 transfer or the '_spender; in ERC20 approve
                // + 12 because address 20 bytes and this is padded to 32
                address toOrSpender = bytesToAddress(_data, 4 + 12);

                // Check if the toOrSpender is in the whitelist
                if (!isWhitelisted[toOrSpender]) {
                    (bool tokenExists,uint etherValue) = IOracle(PublicResolver(_ENS.resolver(_oracleNode)).addr(_oracleNode)).convert(_destination, amount);
                    // If token is supported by our oracle or is ether
                    // Check against the daily spent limit and update accordingly
                    if (tokenExists) {
                        // 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));
                    }
                }
            }
        }

        // If value is send across as a part of this executeTransaction, this will be sent to any payable
        // destination. As a result enforceLimit if destination is not whitelisted.
        if (!isWhitelisted[_destination]) {
            // Require that the value is under remaining limit.
            require(_value <= spendAvailable(), "transfer amount exceeds available spend limit");
            // Update the available limit.
            _setSpendAvailable(spendAvailable().sub(_value));
        }

        require(externalCall(_destination, _value, _data.length, _data), "executing transaction failed");

        emit ExecutedTransaction(_destination, _value, _data);
    }

    /// @dev This function is taken from the Gnosis MultisigWallet: https://github.com/gnosis/MultiSigWallet/
    /// @dev License: https://github.com/gnosis/MultiSigWallet/blob/master/LICENSE
    /// @dev thanks :)
    /// @dev This calls proxies arbitrary transactions to addresses
    /// @param _destination address of the transaction
    /// @param _value ETH amount in wei
    /// @param _dataLength length of the transaction data
    /// @param _data transaction payload binary
    // call has been separated into its own function in order to take advantage
    // of the Solidity's code generator to produce a loop that copies tx.data into memory.
    function externalCall(address _destination, uint _value, uint _dataLength, bytes _data) private returns (bool) {
        bool result;
        assembly {
            let x := mload(0x40)   // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
            let d := add(_data, 32) // First 32 bytes are the padded length of data, so exclude that
            result := call(
                sub(gas, 34710),    // 34710 is the value that solidity is currently emitting
                                    // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
                                    // callNewAccountGas (25000, in case the destination address does not exist and needs creating)
               _destination,
               _value,
               d,
               _dataLength,        // Size of the input (in bytes) - this is what fixes the padding problem
               x,
               0                   // Output is ignored, therefore the output size is zero
               )
        }
        return result;
    }

    /// @dev This function converts to an address
    /// @param _bts bytes
    /// @param _from start position
    function bytesToAddress(bytes _bts, uint _from) private pure returns (address) {
        require(_bts.length >= _from + 20, "slicing out of range");

        uint160 m = 0;
        uint160 b = 0;

        for (uint8 i = 0; i < 20; i++) {
            m *= 256;
            b = uint160 (_bts[_from + i]);
            m += (b);
        }

        return address(m);
    }

    /// @dev This function slicing bytes into uint32
    /// @param _bts some bytes
    /// @param _from  a start position
    function bytesToUint32(bytes _bts, uint _from) private pure returns (uint32) {
        require(_bts.length >= _from + 4, "slicing out of range");

        uint32 m = 0;
        uint32 b = 0;

        for (uint8 i = 0; i < 4; i++) {
            m *= 256;
            b = uint32 (_bts[_from + i]);
            m += (b);
        }

        return m;
    }

    /// @dev This function slices a uint
    /// @param _bts some bytes
    /// @param _from  a start position
    // credit to https://ethereum.stackexchange.com/questions/51229/how-to-convert-bytes-to-uint-in-solidity
    // and Nick Johnson https://ethereum.stackexchange.com/questions/4170/how-to-convert-a-uint-to-bytes-in-solidity/4177#4177
    function sliceUint(bytes _bts, uint _from) private pure returns (uint) {
        require(_bts.length >= _from + 32, "slicing out of range");

        uint x;
        assembly {
           x := mload(add(_bts, add(0x20, _from)))
        }

        return x;
    }
}
//// @title Asset wallet with extra security features and gas top up management.
function externalCall(address _destination, uint _value, uint _dataLength, bytes _data) private returns (bool) {
    bool result;
    assembly {
        let x := mload(0x40)   // "Allocate" memory for output (0x40 is where "free memory" pointer is stored by convention)
        let d := add(_data, 32) // First 32 bytes are the padded length of data, so exclude that
        result := call(
            sub(gas, 34710),    // 34710 is the value that solidity is currently emitting
                                // It includes callGas (700) + callVeryLow (3, to pay for SUB) + callValueTransferGas (9000) +
                                // callNewAccountGas (25000, in case the destination address does not exist and needs creating)
           _destination,
           _value,
           d,
           _dataLength,        // Size of the input (in bytes) - this is what fixes the padding problem
           x,
           0                   // Output is ignored, therefore the output size is zero
           )
    }
    return result;
}

// call has been separated into its own function in order to take advantage
// of the Solidity's code generator to produce a loop that copies tx.data into memory.