-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathOracle.sol
303 lines (278 loc) · 9.59 KB
/
Oracle.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
// 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
*/
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;
// 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;
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 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
)
);
emit OracleRequest(
_specId,
_sender,
requestId,
_payment,
_callbackAddress,
_callbackFunctionId,
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 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];
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");
_;
}
}