// SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import "./Chainlink.sol"; import "./interfaces/ENSInterface.sol"; import "./interfaces/LinkTokenInterface.sol"; import "./interfaces/ChainlinkRequestInterface.sol"; import "./interfaces/PointerInterface.sol"; import { ENSResolver as ENSResolver_Chainlink } from "./vendor/ENSResolver.sol"; /** * @title The ChainlinkClient contract * @notice Contract writers can inherit this contract in order to create requests for the * Chainlink network */ contract ChainlinkClient { using Chainlink for Chainlink.Request; uint256 constant internal LINK = 10**18; uint256 constant private AMOUNT_OVERRIDE = 0; address constant private SENDER_OVERRIDE = address(0); uint256 constant private ARGS_VERSION = 1; bytes32 constant private ENS_TOKEN_SUBNAME = keccak256("link"); bytes32 constant private ENS_ORACLE_SUBNAME = keccak256("oracle"); address constant private LINK_TOKEN_POINTER = 0xC89bD4E1632D3A43CB03AAAd5262cbe4038Bc571; ENSInterface private ens; bytes32 private ensNode; LinkTokenInterface private link; ChainlinkRequestInterface private oracle; uint256 private requestCount = 1; mapping(bytes32 => address) private pendingRequests; event ChainlinkRequested(bytes32 indexed id); event ChainlinkFulfilled(bytes32 indexed id); event ChainlinkCancelled(bytes32 indexed id); /** * @notice Creates a request that can hold additional parameters * @param _specId The Job Specification ID that the request will be created for * @param _callbackAddress The callback address that the response will be sent to * @param _callbackFunctionSignature The callback function signature to use for the callback address * @return A Chainlink Request struct in memory */ function buildChainlinkRequest( bytes32 _specId, address _callbackAddress, bytes4 _callbackFunctionSignature ) internal pure returns (Chainlink.Request memory) { Chainlink.Request memory req; return req.initialize(_specId, _callbackAddress, _callbackFunctionSignature); } /** * @notice Creates a Chainlink request to the stored oracle address * @dev Calls `chainlinkRequestTo` with the stored oracle address * @param _req The initialized Chainlink Request * @param _payment The amount of LINK to send for the request * @return requestId The request ID */ function sendChainlinkRequest(Chainlink.Request memory _req, uint256 _payment) internal returns (bytes32) { return sendChainlinkRequestTo(address(oracle), _req, _payment); } /** * @notice Creates a Chainlink request to the specified oracle address * @dev Generates and stores a request ID, increments the local nonce, and uses `transferAndCall` to * send LINK which creates a request on the target oracle contract. * Emits ChainlinkRequested event. * @param _oracle The address of the oracle for the request * @param _req The initialized Chainlink Request * @param _payment The amount of LINK to send for the request * @return requestId The request ID */ function sendChainlinkRequestTo(address _oracle, Chainlink.Request memory _req, uint256 _payment) internal returns (bytes32 requestId) { requestId = keccak256(abi.encodePacked(this, requestCount)); _req.nonce = requestCount; pendingRequests[requestId] = _oracle; emit ChainlinkRequested(requestId); require(link.transferAndCall(_oracle, _payment, encodeRequest(_req)), "unable to transferAndCall to oracle"); requestCount += 1; return requestId; } /** * @notice Allows a request to be cancelled if it has not been fulfilled * @dev Requires keeping track of the expiration value emitted from the oracle contract. * Deletes the request from the `pendingRequests` mapping. * Emits ChainlinkCancelled event. * @param _requestId The request ID * @param _payment The amount of LINK sent for the request * @param _callbackFunc The callback function specified for the request * @param _expiration The time of the expiration for the request */ function cancelChainlinkRequest( bytes32 _requestId, uint256 _payment, bytes4 _callbackFunc, uint256 _expiration ) internal { ChainlinkRequestInterface requested = ChainlinkRequestInterface(pendingRequests[_requestId]); delete pendingRequests[_requestId]; emit ChainlinkCancelled(_requestId); requested.cancelOracleRequest(_requestId, _payment, _callbackFunc, _expiration); } /** * @notice Sets the stored oracle address * @param _oracle The address of the oracle contract */ function setChainlinkOracle(address _oracle) internal { oracle = ChainlinkRequestInterface(_oracle); } /** * @notice Sets the LINK token address * @param _link The address of the LINK token contract */ function setChainlinkToken(address _link) internal { link = LinkTokenInterface(_link); } /** * @notice Sets the Chainlink token address for the public * network as given by the Pointer contract */ function setPublicChainlinkToken() internal { setChainlinkToken(PointerInterface(LINK_TOKEN_POINTER).getAddress()); } /** * @notice Retrieves the stored address of the LINK token * @return The address of the LINK token */ function chainlinkTokenAddress() internal view returns (address) { return address(link); } /** * @notice Retrieves the stored address of the oracle contract * @return The address of the oracle contract */ function chainlinkOracleAddress() internal view returns (address) { return address(oracle); } /** * @notice Allows for a request which was created on another contract to be fulfilled * on this contract * @param _oracle The address of the oracle contract that will fulfill the request * @param _requestId The request ID used for the response */ function addChainlinkExternalRequest(address _oracle, bytes32 _requestId) internal notPendingRequest(_requestId) { pendingRequests[_requestId] = _oracle; } /** * @notice Sets the stored oracle and LINK token contracts with the addresses resolved by ENS * @dev Accounts for subnodes having different resolvers * @param _ens The address of the ENS contract * @param _node The ENS node hash */ function useChainlinkWithENS(address _ens, bytes32 _node) internal { ens = ENSInterface(_ens); ensNode = _node; bytes32 linkSubnode = keccak256(abi.encodePacked(ensNode, ENS_TOKEN_SUBNAME)); ENSResolver_Chainlink resolver = ENSResolver_Chainlink(ens.resolver(linkSubnode)); setChainlinkToken(resolver.addr(linkSubnode)); updateChainlinkOracleWithENS(); } /** * @notice Sets the stored oracle contract with the address resolved by ENS * @dev This may be called on its own as long as `useChainlinkWithENS` has been called previously */ function updateChainlinkOracleWithENS() internal { bytes32 oracleSubnode = keccak256(abi.encodePacked(ensNode, ENS_ORACLE_SUBNAME)); ENSResolver_Chainlink resolver = ENSResolver_Chainlink(ens.resolver(oracleSubnode)); setChainlinkOracle(resolver.addr(oracleSubnode)); } /** * @notice Encodes the request to be sent to the oracle contract * @dev The Chainlink node expects values to be in order for the request to be picked up. Order of types * will be validated in the oracle contract. * @param _req The initialized Chainlink Request * @return The bytes payload for the `transferAndCall` method */ function encodeRequest(Chainlink.Request memory _req) private view returns (bytes memory) { return abi.encodeWithSelector( oracle.oracleRequest.selector, SENDER_OVERRIDE, // Sender value - overridden by onTokenTransfer by the requesting contract's address AMOUNT_OVERRIDE, // Amount value - overridden by onTokenTransfer by the actual amount of LINK sent _req.id, _req.callbackAddress, _req.callbackFunctionId, _req.nonce, ARGS_VERSION, _req.buf.buf); } /** * @notice Ensures that the fulfillment is valid for this contract * @dev Use if the contract developer prefers methods instead of modifiers for validation * @param _requestId The request ID for fulfillment */ function validateChainlinkCallback(bytes32 _requestId) internal recordChainlinkFulfillment(_requestId) // solhint-disable-next-line no-empty-blocks {} /** * @dev Reverts if the sender is not the oracle of the request. * Emits ChainlinkFulfilled event. * @param _requestId The request ID for fulfillment */ modifier recordChainlinkFulfillment(bytes32 _requestId) { require(msg.sender == pendingRequests[_requestId], "Source must be the oracle of the request"); delete pendingRequests[_requestId]; emit ChainlinkFulfilled(_requestId); _; } /** * @dev Reverts if the request is already pending * @param _requestId The request ID for fulfillment */ modifier notPendingRequest(bytes32 _requestId) { require(pendingRequests[_requestId] == address(0), "Request is already pending"); _; } }