contract BaseAccount is AccountState, AccountInitialize, TokenReceiverHooks {
    using SafeMath for uint256;
    using ECDSA for bytes32;
    using BytesLib for bytes;

    // Include a CHAIN_ID const
    uint256 constant private CHAIN_ID = 1;

    modifier onlySelf {
        require(msg.sender == address(this), "BA: Only self allowed");
        _;
    }

    modifier onlyAuthKeySender {
        require(_isValidAuthKey(msg.sender), "BA: Auth key is invalid");
        _;
    }

    modifier onlyAuthKeySenderOrSelf {
        require(_isValidAuthKey(msg.sender) || msg.sender == address(this), "BA: Auth key or self is invalid");
        _;
    }

    // This is required for funds sent to this contract
    function () external payable {}

    /**
     *  Getters
     */

    /// @dev Get the chain ID constant
    /// @return The chain id
    function getChainId() public pure returns (uint256) {
        return CHAIN_ID;
    }

    /**
     *  Public functions
     */

    /// @dev Add an auth key to the list of auth keys
    /// @param _authKey Address of the auth key to add
    function addAuthKey(address _authKey) external onlyAuthKeySenderOrSelf {
        require(authKeys[_authKey] == false, "BA: Auth key already added");
        authKeys[_authKey] = true;
        numAuthKeys += 1;
        emit AuthKeyAdded(_authKey);
    }

    /// @dev Remove an auth key from the list of auth keys
    /// @param _authKey Address of the auth key to remove
    function removeAuthKey(address _authKey) external onlyAuthKeySenderOrSelf {
        require(authKeys[_authKey] == true, "BA: Auth key not yet added");
        require(numAuthKeys > 1, "BA: Cannot remove last auth key");
        authKeys[_authKey] = false;
        numAuthKeys -= 1;
        emit AuthKeyRemoved(_authKey);
    }

    /**
     *  Internal functions
     */

    /// @dev Check if an auth key is valid
    /// @param _authKey Address of the auth key to validate
    /// @return True if the auth key is valid
    function _isValidAuthKey(address _authKey) internal view returns (bool) {
        return authKeys[_authKey];
    }

    /// @dev Execute a transaction without a refund
    /// @notice This is the transaction sent from the CBA
    /// @param _destination Destination of the transaction
    /// @param _value Value of the transaction
    /// @param _gasLimit Gas limit of the transaction
    /// @param _data Data of the transaction
    /// @return Response of the call
    function _executeTransaction(
        address _destination,
        uint256 _value,
        uint256 _gasLimit,
        bytes memory _data
    )
        internal
        returns (bytes memory)
    {
        (bool success, bytes memory res) = _destination.call.gas(_gasLimit).value(_value)(_data);

        // Get the revert message of the call and revert with it if the call failed
        string memory _revertMsg = _getRevertMsg(res);
        require(success, _revertMsg);

        return res;
    }

    /// @dev Issue a refund
    /// @param _startGas Starting gas at the beginning of the transaction
    /// @param _gasPrice Gas price to use when sending a refund
    /// @param _gasOverhead Gas overhead of the transaction calculated offchain
    /// @param _feeTokenAddress Address of the token used to pay a fee
    /// @param _feeTokenRate Rate of the token (in tokenGasPrice/ethGasPrice) used to pay a fee
    function _issueRefund(
        uint256 _startGas,
        uint256 _gasPrice,
        uint256 _gasOverhead,
        address _feeTokenAddress,
        uint256 _feeTokenRate
    )
        internal
    {
        uint256 _gasUsed = _startGas.sub(gasleft()).add(_gasOverhead);

        // Pay refund in ETH if _feeTokenAddress is 0. Else, pay in the token
        if (_feeTokenAddress == address(0)) {
            require(_gasUsed.mul(_gasPrice) <= address(this).balance, "BA: Insufficient gas (ETH) for refund");
            // NOTE: The return value is not checked because the relayer should not propogate a transaction that will revert
            // NOTE: and malicious behavior by the relayer here will cost the relayer, as the fee is already calculated
            msg.sender.call.value(_gasUsed.mul(_gasPrice))("");
        } else {
            IERC20 feeToken = IERC20(_feeTokenAddress);
            uint256 totalTokenFee = _gasUsed.mul(_gasPrice).mul(_feeTokenRate);
            require(totalTokenFee <= feeToken.balanceOf(address(this)), "BA: Insufficient gas (token) for refund");
            // NOTE: The return value is not checked because the relayer should not propogate a transaction that will revert
            feeToken.transfer(msg.sender, totalTokenFee);
        }
    }

    /// @dev Get the revert message from a call
    /// @notice This is needed in order to get the human-readable revert message from a call
    /// @param _res Response of the call
    /// @return Revert message string
    function _getRevertMsg(bytes memory _res) internal view returns (string memory) {
        // If the _res length is less than 68, then the transaction failed silently (without a revert message)
        if (_res.length < 68) return 'BA: Transaction reverted silently';

        string memory revertMsg;
        assembly {                                   // 36 is the location of the revert message in _res (_res.length (32 bytes) + topic (4 bytes))
            let offset := mload(add(_res, 36))       // Get the offset of the revert message by adding the location in _res
            revertMsg := add(_res, add(36, offset))  // Assign data ptr to the start of the revert message
        }

        return revertMsg;
    }
}
function _issueRefund(
    uint256 _startGas,
    uint256 _gasPrice,
    uint256 _gasOverhead,
    address _feeTokenAddress,
    uint256 _feeTokenRate
)
    internal
{
    uint256 _gasUsed = _startGas.sub(gasleft()).add(_gasOverhead);

    // Pay refund in ETH if _feeTokenAddress is 0. Else, pay in the token
    if (_feeTokenAddress == address(0)) {
        require(_gasUsed.mul(_gasPrice) <= address(this).balance, "BA: Insufficient gas (ETH) for refund");
        // NOTE: The return value is not checked because the relayer should not propogate a transaction that will revert
        // NOTE: and malicious behavior by the relayer here will cost the relayer, as the fee is already calculated
        msg.sender.call.value(_gasUsed.mul(_gasPrice))("");
    } else {
        IERC20 feeToken = IERC20(_feeTokenAddress);
        uint256 totalTokenFee = _gasUsed.mul(_gasPrice).mul(_feeTokenRate);
        require(totalTokenFee <= feeToken.balanceOf(address(this)), "BA: Insufficient gas (token) for refund");
        // NOTE: The return value is not checked because the relayer should not propogate a transaction that will revert
        feeToken.transfer(msg.sender, totalTokenFee);
    }
}

/// @dev Issue a refund
/// @param _startGas Starting gas at the beginning of the transaction
/// @param _gasPrice Gas price to use when sending a refund
/// @param _gasOverhead Gas overhead of the transaction calculated offchain
/// @param _feeTokenAddress Address of the token used to pay a fee
/// @param _feeTokenRate Rate of the token (in tokenGasPrice/ethGasPrice) used to pay a fee