Contract Source Code:
// SPDX-License-Identifier: MIT
pragma solidity 0.6.6;
import "./LinkTokenReceiver.sol";
import "./interfaces/ChainlinkRequestInterface.sol";
import "./interfaces/OracleInterface.sol";
import "./interfaces/LinkTokenInterface.sol";
import "./interfaces/WithdrawalInterface.sol";
import "./vendor/Ownable.sol";
import "./vendor/SafeMathChainlink.sol";
/**
* @title The Chainlink Oracle contract
* @notice Node operators can deploy this contract to fulfill requests sent to them.
* This contract is optimized for rollups (like Arbitrum and Optimism), it will be inefficient everywhere else
*/
contract Oracle is ChainlinkRequestInterface, OracleInterface, Ownable, LinkTokenReceiver, WithdrawalInterface {
using SafeMathChainlink for uint256;
uint256 constant public EXPIRY_TIME = 5 minutes;
uint256 constant private MINIMUM_CONSUMER_GAS_LIMIT = 400000;
uint32 internal constant MAX_INDEX_SIG = 268_435_455; // 0x0FFFFFFF
// We initialize fields to 1 instead of 0 so that the first invocation
// does not cost more gas.
uint256 constant private ONE_FOR_CONSISTENT_GAS_COST = 1;
LinkTokenInterface internal LinkToken;
mapping(bytes32 => bytes32) private commitments;
mapping(address => bool) private authorizedNodes;
uint256 private withdrawableTokens = ONE_FOR_CONSISTENT_GAS_COST;
// Struct packed into 2 slots
// Cannot pack into 1 because we need payment to be > uint72 and expiration uint32
struct PackedOracleRequest {
address callbackAddress; // slot1: 160
bytes4 callbackFunctionId; // slot1: 160 + 32
uint128 payment; // slot2: 128
uint128 expiration; // slot2: 128 + 128
}
uint32 public orderIndex; // type(uint32).max 4,294,967,295
mapping(bytes32 => PackedOracleRequest) private s_packedRequests;
mapping(uint32 => bytes32) private s_indexRequests;
event OracleRequest(
bytes32 indexed specId,
address requester,
bytes32 requestId,
uint256 payment,
address callbackAddr,
bytes4 callbackFunctionId,
uint256 cancelExpiration,
uint256 dataVersion,
bytes data
);
event CancelOracleRequest(
bytes32 indexed requestId
);
/**
* @notice Deploy with the address of the LINK token
* @dev Sets the LinkToken address for the imported LinkTokenInterface
* @param _link The address of the LINK token
*/
constructor(address _link)
public
Ownable()
{
LinkToken = LinkTokenInterface(_link); // external but already deployed and unalterable
}
/**
* @notice Fallback function
* @dev When calldata.length === 36 bytes, forwards message to fulfillOracleRequestShort
*/
fallback() external payable {
if(msg.data.length == 36) { // 36 = sig + 1 word
uint32 index = uint32(msg.sig);
// if sig is gt 0x0FFFFFFF (the max request index we allow) then it's not one of our calls
if (index > MAX_INDEX_SIG)
return;
// call fulfillOracleRequestShort
bytes32 _data = abi.decode(msg.data[4:], (bytes32));
fulfillOracleRequestShort(s_indexRequests[index], _data);
delete s_indexRequests[index];
}
}
/**
* @notice Creates the Chainlink request
* @dev Stores the hash of the params as the on-chain commitment for the request.
* Emits OracleRequest event for the Chainlink node to detect.
* @param _sender The sender of the request
* @param _payment The amount of payment given (specified in wei)
* @param _specId The Job Specification ID
* @param _callbackAddress The callback address for the response
* @param _callbackFunctionId The callback function ID for the response
* @param _nonce The nonce sent by the requester
* @param _dataVersion The specified data version
* @param _data The CBOR payload of the request
*/
function oracleRequest(
address _sender,
uint256 _payment,
bytes32 _specId,
address _callbackAddress,
bytes4 _callbackFunctionId,
uint256 _nonce,
uint256 _dataVersion,
bytes calldata _data
)
external
override
onlyLINK()
checkCallbackAddress(_callbackAddress)
{
bytes32 requestId = keccak256(abi.encodePacked(_sender, _nonce));
require(commitments[requestId] == 0, "Must use a unique ID");
// solhint-disable-next-line not-rely-on-time
uint256 expiration = now.add(EXPIRY_TIME);
commitments[requestId] = keccak256(
abi.encodePacked(
_payment,
_callbackAddress,
_callbackFunctionId,
expiration
)
);
s_packedRequests[requestId] = PackedOracleRequest(
_callbackAddress,
_callbackFunctionId,
uint128(_payment),
uint128(expiration)
);
uint32 _index = orderIndex;
s_indexRequests[_index] = requestId;
// we know there are no valid signatures in this contract with a leading 0 so we
// limit max index to 0x0FFFFFFF (MAX_INDEX_SIG)
// if current index is MAX_INDEX_SIG then we reset index to zero
if (_index == MAX_INDEX_SIG) {
orderIndex = 0;
} else {
orderIndex = _index + 1; // can't overflow since we always stay below 0x0FFFFFFF + 1
}
emit OracleRequest(
_specId,
_sender,
requestId,
_payment,
_callbackAddress,
// highjack `_callbackFunctionId` since our custom nodes read that param from storage and
// we need somewhere to emit `_index` without modifying the event (it would break the node)
bytes4(_index),
expiration,
_dataVersion,
_data);
}
/**
* @notice Called by the Chainlink node to fulfill requests
* @dev Given params must hash back to the commitment stored from `oracleRequest`.
* Will call the callback address' callback function without bubbling up error
* checking in a `require` so that the node can get paid.
* @param _requestId The fulfillment request ID that must match the requester's
* @param _payment The payment amount that will be released for the oracle (specified in wei)
* @param _callbackAddress The callback address to call for fulfillment
* @param _callbackFunctionId The callback function ID to use for fulfillment
* @param _expiration The expiration that the node should respond by before the requester can cancel
* @param _data The data to return to the consuming contract
* @return Status if the external call was successful
*/
function fulfillOracleRequest(
bytes32 _requestId,
uint256 _payment,
address _callbackAddress,
bytes4 _callbackFunctionId,
uint256 _expiration,
bytes32 _data
)
external
onlyAuthorizedNode
override
isValidRequest(_requestId)
returns (bool)
{
bytes32 paramsHash = keccak256(
abi.encodePacked(
_payment,
_callbackAddress,
_callbackFunctionId,
_expiration
)
);
require(commitments[_requestId] == paramsHash, "Params do not match request ID");
withdrawableTokens = withdrawableTokens.add(_payment);
delete commitments[_requestId];
//require(gasleft() >= MINIMUM_CONSUMER_GAS_LIMIT, "Must provide consumer enough gas");
// All updates to the oracle's fulfillment should come before calling the
// callback(addr+functionId) as it is untrusted.
// See: https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern
(bool success, ) = _callbackAddress.call(abi.encodeWithSelector(_callbackFunctionId, _requestId, _data)); // solhint-disable-line avoid-low-level-calls
return success;
}
/**
* @notice Called by the Chainlink node to fulfill requests
* @dev Matches functionality of fulfillOracleRequest with the exception of
* loading _payment, _callbackAddress, _callbackFunctionId and _expiration from
* storage. Ideal for L2's that charge for L1 calldata.
* Given params must hash back to the commitment stored from `oracleRequest`.
* Will call the callback address' callback function without bubbling up error
* checking in a `require` so that the node can get paid.
* @param _requestId The fulfillment request ID that must match the requester's
* @param _data The data to return to the consuming contract
* @return Status if the external call was successful
*/
function fulfillOracleRequestShort(
bytes32 _requestId,
bytes32 _data
)
public
onlyAuthorizedNode
isValidRequest(_requestId)
returns (bool)
{
PackedOracleRequest memory request = s_packedRequests[_requestId];
bytes32 paramsHash = keccak256(
abi.encodePacked(
uint(request.payment),
request.callbackAddress,
request.callbackFunctionId,
uint(request.expiration)
)
);
require(commitments[_requestId] == paramsHash, "Params do not match request ID");
withdrawableTokens = withdrawableTokens.add(uint(request.payment));
delete commitments[_requestId];
delete s_packedRequests[_requestId];
//require(gasleft() >= MINIMUM_CONSUMER_GAS_LIMIT, "Must provide consumer enough gas");
// All updates to the oracle's fulfillment should come before calling the
// callback(addr+functionId) as it is untrusted.
// See: https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern
(bool success, ) = request.callbackAddress.call(abi.encodeWithSelector(request.callbackFunctionId, _requestId, _data)); // solhint-disable-line avoid-low-level-calls
return success;
}
/**
* @notice Use this to check if a node is authorized for fulfilling requests
* @param _node The address of the Chainlink node
* @return The authorization status of the node
*/
function getAuthorizationStatus(address _node)
external
view
override
returns (bool)
{
return authorizedNodes[_node];
}
/**
* @notice Sets the fulfillment permission for a given node. Use `true` to allow, `false` to disallow.
* @param _node The address of the Chainlink node
* @param _allowed Bool value to determine if the node can fulfill requests
*/
function setFulfillmentPermission(address _node, bool _allowed)
external
override
onlyOwner()
{
authorizedNodes[_node] = _allowed;
}
/**
* @notice Allows the node operator to withdraw earned LINK to a given address
* @dev The owner of the contract can be another wallet and does not have to be a Chainlink node
* @param _recipient The address to send the LINK token to
* @param _amount The amount to send (specified in wei)
*/
function withdraw(address _recipient, uint256 _amount)
external
override(OracleInterface, WithdrawalInterface)
onlyOwner
hasAvailableFunds(_amount)
{
withdrawableTokens = withdrawableTokens.sub(_amount);
assert(LinkToken.transfer(_recipient, _amount));
}
/**
* @notice Displays the amount of LINK that is available for the node operator to withdraw
* @dev We use `ONE_FOR_CONSISTENT_GAS_COST` in place of 0 in storage
* @return The amount of withdrawable LINK on the contract
*/
function withdrawable()
external
view
override(OracleInterface, WithdrawalInterface)
onlyOwner()
returns (uint256)
{
return withdrawableTokens.sub(ONE_FOR_CONSISTENT_GAS_COST);
}
/**
* @notice Allows requesters to cancel requests sent to this oracle contract. Will transfer the LINK
* sent for the request back to the requester's address.
* @dev Given params must hash to a commitment stored on the contract in order for the request to be valid
* Emits CancelOracleRequest event.
* @param _requestId The request ID
* @param _payment The amount of payment given (specified in wei)
* @param _callbackFunc The requester's specified callback address
* @param _expiration The time of the expiration for the request
*/
function cancelOracleRequest(
bytes32 _requestId,
uint256 _payment,
bytes4 _callbackFunc,
uint256 _expiration
)
external
override
{
bytes32 paramsHash = keccak256(
abi.encodePacked(
_payment,
msg.sender,
_callbackFunc,
_expiration)
);
require(paramsHash == commitments[_requestId], "Params do not match request ID");
// solhint-disable-next-line not-rely-on-time
require(_expiration <= now, "Request is not expired");
delete commitments[_requestId];
delete s_packedRequests[_requestId];
emit CancelOracleRequest(_requestId);
assert(LinkToken.transfer(msg.sender, _payment));
}
/**
* @notice Returns the address of the LINK token
* @dev This is the public implementation for chainlinkTokenAddress, which is
* an internal method of the ChainlinkClient contract
*/
function getChainlinkToken()
public
view
override
returns (address)
{
return address(LinkToken);
}
// MODIFIERS
/**
* @dev Reverts if amount requested is greater than withdrawable balance
* @param _amount The given amount to compare to `withdrawableTokens`
*/
modifier hasAvailableFunds(uint256 _amount) {
require(withdrawableTokens >= _amount.add(ONE_FOR_CONSISTENT_GAS_COST), "Amount requested is greater than withdrawable balance");
_;
}
/**
* @dev Reverts if request ID does not exist
* @param _requestId The given request ID to check in stored `commitments`
*/
modifier isValidRequest(bytes32 _requestId) {
require(commitments[_requestId] != 0, "Must have a valid requestId");
_;
}
/**
* @dev Reverts if `msg.sender` is not authorized to fulfill requests
*/
modifier onlyAuthorizedNode() {
require(authorizedNodes[msg.sender] || msg.sender == owner(), "Not an authorized node to fulfill requests");
_;
}
/**
* @dev Reverts if the callback address is the LINK token
* @param _to The callback address
*/
modifier checkCallbackAddress(address _to) {
require(_to != address(LinkToken), "Cannot callback to LINK");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
abstract contract LinkTokenReceiver {
bytes4 constant private ORACLE_REQUEST_SELECTOR = 0x40429946;
uint256 constant private SELECTOR_LENGTH = 4;
uint256 constant private EXPECTED_REQUEST_WORDS = 2;
uint256 constant private MINIMUM_REQUEST_LENGTH = SELECTOR_LENGTH + (32 * EXPECTED_REQUEST_WORDS);
/**
* @notice Called when LINK is sent to the contract via `transferAndCall`
* @dev The data payload's first 2 words will be overwritten by the `_sender` and `_amount`
* values to ensure correctness. Calls oracleRequest.
* @param _sender Address of the sender
* @param _amount Amount of LINK sent (specified in wei)
* @param _data Payload of the transaction
*/
function onTokenTransfer(
address _sender,
uint256 _amount,
bytes memory _data
)
public
onlyLINK
validRequestLength(_data)
permittedFunctionsForLINK(_data)
{
assembly {
// solhint-disable-next-line avoid-low-level-calls
mstore(add(_data, 36), _sender) // ensure correct sender is passed
// solhint-disable-next-line avoid-low-level-calls
mstore(add(_data, 68), _amount) // ensure correct amount is passed
}
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(this).delegatecall(_data); // calls oracleRequest
require(success, "Unable to create request");
}
function getChainlinkToken() public view virtual returns (address);
/**
* @dev Reverts if not sent from the LINK token
*/
modifier onlyLINK() {
require(msg.sender == getChainlinkToken(), "Must use LINK token");
_;
}
/**
* @dev Reverts if the given data does not begin with the `oracleRequest` function selector
* @param _data The data payload of the request
*/
modifier permittedFunctionsForLINK(bytes memory _data) {
bytes4 funcSelector;
assembly {
// solhint-disable-next-line avoid-low-level-calls
funcSelector := mload(add(_data, 32))
}
require(funcSelector == ORACLE_REQUEST_SELECTOR, "Must use whitelisted functions");
_;
}
/**
* @dev Reverts if the given payload is less than needed to create a request
* @param _data The request payload
*/
modifier validRequestLength(bytes memory _data) {
require(_data.length >= MINIMUM_REQUEST_LENGTH, "Invalid request length");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface ChainlinkRequestInterface {
function oracleRequest(
address sender,
uint256 requestPrice,
bytes32 serviceAgreementID,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 nonce,
uint256 dataVersion,
bytes calldata data
) external;
function cancelOracleRequest(
bytes32 requestId,
uint256 payment,
bytes4 callbackFunctionId,
uint256 expiration
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface OracleInterface {
function fulfillOracleRequest(
bytes32 requestId,
uint256 payment,
address callbackAddress,
bytes4 callbackFunctionId,
uint256 expiration,
bytes32 data
) external returns (bool);
function getAuthorizationStatus(address node) external view returns (bool);
function setFulfillmentPermission(address node, bool allowed) external;
function withdraw(address recipient, uint256 amount) external;
function withdrawable() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface LinkTokenInterface {
function allowance(address owner, address spender) external view returns (uint256 remaining);
function approve(address spender, uint256 value) external returns (bool success);
function balanceOf(address owner) external view returns (uint256 balance);
function decimals() external view returns (uint8 decimalPlaces);
function decreaseApproval(address spender, uint256 addedValue) external returns (bool success);
function increaseApproval(address spender, uint256 subtractedValue) external;
function name() external view returns (string memory tokenName);
function symbol() external view returns (string memory tokenSymbol);
function totalSupply() external view returns (uint256 totalTokensIssued);
function transfer(address to, uint256 value) external returns (bool success);
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success);
function transferFrom(address from, address to, uint256 value) external returns (bool success);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface WithdrawalInterface {
/**
* @notice transfer LINK held by the contract belonging to msg.sender to
* another address
* @param recipient is the address to send the LINK to
* @param amount is the amount of LINK to send
*/
function withdraw(address recipient, uint256 amount) external;
/**
* @notice query the available amount of LINK to withdraw by msg.sender
*/
function withdrawable() external view returns (uint256);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be aplied to your functions to restrict their use to
* the owner.
*
* This contract has been modified to remove the revokeOwnership function
*/
contract Ownable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor () internal {
_owner = msg.sender;
emit OwnershipTransferred(address(0), _owner);
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(isOwner(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Returns true if the caller is the current owner.
*/
function isOwner() public view returns (bool) {
return msg.sender == _owner;
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public onlyOwner {
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
*/
function _transferOwnership(address newOwner) internal {
require(newOwner != address(0), "Ownable: new owner is the zero address");
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
/**
* @dev Wrappers over Solidity's arithmetic operations with added overflow
* checks.
*
* Arithmetic operations in Solidity wrap on overflow. This can easily result
* in bugs, because programmers usually assume that an overflow raises an
* error, which is the standard behavior in high level programming languages.
* `SafeMath` restores this intuition by reverting the transaction when an
* operation overflows.
*
* Using this library instead of the unchecked operations eliminates an entire
* class of bugs, so it's recommended to use it always.
*/
library SafeMathChainlink {
/**
* @dev Returns the addition of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `+` operator.
*
* Requirements:
* - Addition cannot overflow.
*/
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");
return c;
}
/**
* @dev Returns the subtraction of two unsigned integers, reverting on
* overflow (when the result is negative).
*
* Counterpart to Solidity's `-` operator.
*
* Requirements:
* - Subtraction cannot overflow.
*/
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;
return c;
}
/**
* @dev Returns the multiplication of two unsigned integers, reverting on
* overflow.
*
* Counterpart to Solidity's `*` operator.
*
* Requirements:
* - Multiplication cannot overflow.
*/
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
if (a == 0) {
return 0;
}
uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");
return c;
}
/**
* @dev Returns the integer division of two unsigned integers. Reverts on
* division by zero. The result is rounded towards zero.
*
* Counterpart to Solidity's `/` operator. Note: this function uses a
* `revert` opcode (which leaves remaining gas untouched) while Solidity
* uses an invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// Solidity only automatically asserts when dividing by 0
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
/**
* @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
* Reverts when dividing by zero.
*
* Counterpart to Solidity's `%` operator. This function uses a `revert`
* opcode (which leaves remaining gas untouched) while Solidity uses an
* invalid opcode to revert (consuming all remaining gas).
*
* Requirements:
* - The divisor cannot be zero.
*/
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}