/
VRFCoordinator.sol
391 lines (355 loc) · 15.2 KB
/
VRFCoordinator.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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "./SafeMathVision.sol";
import "./VRC20Interface.sol";
import "./BlockHashStoreInterface.sol";
import "./Owned.sol";
import "./VRF.sol";
import "./VRFRequestIDBase.sol";
import "./VRFConsumerBase.sol";
/**
* @title VRFCoordinator coordinates on-chain verifiable-randomness requests
* @title with off-chain responses
*/
contract VRFCoordinator is VRF, VRFRequestIDBase, Owned {
using SafeMathVision for uint256;
VictorMid internal victorMid;
VRC20Interface internal token;
BlockHashStoreInterface internal blockHashStore;
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);
constructor(address _vct, address _victorMid, address _blockHashStore) public {
token = VRC20Interface(_vct);
victorMid = VictorMid(_victorMid);
blockHashStore = BlockHashStoreInterface(_blockHashStore);
}
struct Callback { // Tracks an ongoing request
address callbackContract; // Requesting contract, which will receive response
// Amount of LINK paid at request time. Total LINK = 1e9 * 1e18 < 2^96, so
// this representation is adequate, and saves a word of storage when this
// field follows the 160-bit callbackContract address.
uint96 randomnessFee;
// Commitment to seed passed to oracle by this contract, and the number of
// the block in which the request appeared. This is the keccak256 of the
// concatenation of those values. Storing this commitment saves a word of
// storage.
bytes32 seedAndBlockNum;
}
struct ServiceAgreement { // Tracks oracle commitments to VRF service
address vRFOracle; // Oracle committing to respond with VRF service
uint96 fee; // Minimum payment for oracle response. Total LINK=1e9*1e18<2^96
bytes32 jobID; // ID of corresponding chainlink job in oracle's DB
}
mapping(bytes32 /* (provingKey, seed) */ => Callback) public callbacks;
mapping(bytes32 /* provingKey */ => ServiceAgreement)
public serviceAgreements;
mapping(address /* oracle */ => uint256 /* LINK balance */)
public withdrawableTokens;
mapping(bytes32 /* provingKey */ => mapping(address /* consumer */ => uint256))
private nonces;
// The oracle only needs the jobID to look up the VRF, but specifying public
// key as well prevents a malicious oracle from inducing VRF outputs from
// another oracle by reusing the jobID.
event VRFRequest(
bytes32 keyHash,
bytes32 seed,
bytes32 indexed jobID,
address sender,
uint256 fee,
bytes32 requestID);
event NewServiceAgreement(bytes32 keyHash, uint256 fee);
event RandomnessRequestFulfilled(bytes32 requestId, uint256 output);
/**
* @notice Commits calling address to serve randomness
* @param _fee minimum LINK payment required to serve randomness
* @param _oracle the address of the Chainlink node with the proving key and job
* @param _publicProvingKey public key used to prove randomness
* @param _jobID ID of the corresponding chainlink job in the oracle's db
*/
function registerProvingKey(
uint256 _fee, address _oracle, /*uint256[2] calldata _publicProvingKey,*/
bytes calldata _publicProvingKey, bytes32 _jobID
)
external
onlyOwner()
{
//bytes32 keyHash = hashOfKey(_publicProvingKey);
require(_publicProvingKey.length == 64, "_publicProvingKey.length should be 64 bytes, P.X||P.Y");
bytes32 keyHash = hashOfKeyBytes(_publicProvingKey);
address oldVRFOracle = serviceAgreements[keyHash].vRFOracle;
require(oldVRFOracle == address(0), "please register a new key");
require(_oracle != address(0), "_oracle must not be 0x0");
serviceAgreements[keyHash].vRFOracle = _oracle;
serviceAgreements[keyHash].jobID = _jobID;
// Yes, this revert message doesn't fit in a word
require(_fee <= 1e17,
"you can't charge more than all the LINK in the world, greedy");
serviceAgreements[keyHash].fee = uint96(_fee);
emit NewServiceAgreement(keyHash, _fee);
}
/**
* @notice Creates the VRF request
* @dev Stores the hash of the params as the on-chain commitment for the request.
* Emits VRFRequest event for the Victorlink node to detect.
* @param _sender The sender of the request
* @param _feePaid The amount of payment given (specified in VCT)
* @param _callbackAddress The callback address for the response
* param _callbackFunctionId The callback function ID for the response
* @param _data The CBOR payload of the request
*/
function vrfRequest(
address _sender,
uint256 _feePaid,
bytes32 /*_specId*/,
address _callbackAddress,
bytes4 /*_callbackFunctionId*/,
uint256 /*_nonce*/,
uint256 /*_dataVersion*/,
bytes calldata _data
)
external
onlyVictorMid
checkCallbackAddress(_callbackAddress)
{
(bytes32 keyHash, uint256 consumerSeed) = abi.decode(_data, (bytes32, uint256));
randomnessRequest(keyHash, consumerSeed, _feePaid, _sender);
}
/**
* @notice Called by LINK.transferAndCall, on successful LINK transfer
*
* @dev To invoke this, use the requestRandomness method in VRFConsumerBase.
*
* @dev The VRFCoordinator will call back to the calling contract when the
* @dev oracle responds, on the method fulfillRandomness. See
* @dev VRFConsumerBase.fulfilRandomness for its signature. Your consuming
* @dev contract should inherit from VRFConsumerBase, and implement
* @dev fulfilRandomness.
*
* @param _sender address: who sent the LINK (must be a contract)
* @param _fee amount of LINK sent
* @param _data abi-encoded call to randomnessRequest
*/
function onTokenTransfer(address _sender, uint256 _fee, bytes memory _data)
public
onlyVictorMid
validRequestLength(_data)
permittedFunctionsForLINK(_data)
{
bytes32 keyHash;
assembly {
keyHash := mload(add(_data,100))
}
require(serviceAgreements[keyHash].vRFOracle != address(0), "please use a registered keyHash");
assembly { // solhint-disable-line no-inline-assembly
mstore(add(_data, 36), _sender) // ensure correct sender is passed
mstore(add(_data, 68), _fee) // ensure correct amount is passed
}
// solhint-disable-next-line avoid-low-level-calls
(bool status, ) = address(this).delegatecall(_data);
require(status, "Unable to create request"); // calls vrfRequest
}
/**
* @notice creates the chainlink request for randomness
*
* @param _keyHash ID of the VRF public key against which to generate output
* @param _consumerSeed Input to the VRF, from which randomness is generated
* @param _feePaid Amount of LINK sent with request. Must exceed fee for key
* @param _sender Requesting contract; to be called back with VRF output
*
* @dev _consumerSeed is mixed with key hash, sender address and nonce to
* @dev obtain preSeed, which is passed to VRF oracle, which mixes it with the
* @dev hash of the block containing this request, to compute the final seed.
*
* @dev The requestId used to store the request data is constructed from the
* @dev preSeed and keyHash.
*/
function randomnessRequest(
bytes32 _keyHash,
uint256 _consumerSeed,
uint256 _feePaid,
address _sender
)
internal
sufficientVCT(_feePaid, _keyHash)
{
uint256 nonce = nonces[_keyHash][_sender];
uint256 preSeed = makeVRFInputSeed(_keyHash, _consumerSeed, _sender, nonce);
bytes32 requestId = makeRequestId(_keyHash, preSeed);
// Cryptographically guaranteed by preSeed including an increasing nonce
assert(callbacks[requestId].callbackContract == address(0));
callbacks[requestId].callbackContract = _sender;
assert(_feePaid < 1e17); // Total LINK fits in uint96
callbacks[requestId].randomnessFee = uint96(_feePaid);
callbacks[requestId].seedAndBlockNum = keccak256(abi.encodePacked(
preSeed, block.number));
emit VRFRequest(_keyHash, bytes32(preSeed), serviceAgreements[_keyHash].jobID,
_sender, _feePaid, requestId);
nonces[_keyHash][_sender] = nonces[_keyHash][_sender].add(1);
}
// Offsets into fulfillRandomnessRequest's _proof of various values
//
// Public key. Skips byte array's length prefix.
uint256 public constant PUBLIC_KEY_OFFSET = 0x20;
// Seed is 7th word in proof, plus word for length, (6+1)*0x20=0xe0
uint256 public constant PRESEED_OFFSET = 0xe0;
/**
* @notice Called by the chainlink node to fulfill requests
*
* @param _proof the proof of randomness. Actual random output built from this
*
* @dev The structure of _proof corresponds to vrf.MarshaledOnChainResponse,
* @dev in the node source code. I.e., it is a vrf.MarshaledProof with the
* @dev seed replaced by the preSeed, followed by the hash of the requesting
* @dev block.
*/
function fulfillRandomnessRequest(bytes memory _proof) public {
(bytes32 currentKeyHash, Callback memory callback, bytes32 requestId,
uint256 randomness) = getRandomnessFromProof(_proof);
// Pay oracle
address oadd = serviceAgreements[currentKeyHash].vRFOracle;
withdrawableTokens[oadd] = withdrawableTokens[oadd].add(
callback.randomnessFee);
// Forget request. Must precede callback (prevents reentrancy)
delete callbacks[requestId];
callBackWithRandomness(requestId, randomness, callback.callbackContract);
emit RandomnessRequestFulfilled(requestId, randomness);
}
function callBackWithRandomness(bytes32 requestId, uint256 randomness,
address consumerContract) internal {
// Dummy variable; allows access to method selector in next line. See
// https://github.com/ethereum/solidity/issues/3506#issuecomment-553727797
VRFConsumerBase v;
bytes memory resp = abi.encodeWithSelector(
v.rawFulfillRandomness.selector, requestId, randomness);
// The bound b here comes from https://eips.ethereum.org/EIPS/eip-150. The
// actual gas available to the consuming contract will be b-floor(b/64).
// This is chosen to leave the consuming contract ~200k gas, after the cost
// of the call itself.
//uint256 b = 206000;
//require(gasleft() >= b, "not enough gas for consumer");
// A low-level call is necessary, here, because we don't want the consuming
// contract to be able to revert this execution, and thus deny the oracle
// payment for a valid randomness response. This also necessitates the above
// check on the gasleft, as otherwise there would be no indication if the
// callback method ran out of gas.
//
// solhint-disable-next-line avoid-low-level-calls
(bool success,) = consumerContract.call(resp);
// Avoid unused-local-variable warning. (success is only present to prevent
// a warning that the return value of consumerContract.call is unused.)
(success);
}
function getRandomnessFromProof(bytes memory _proof)
internal view returns (bytes32 currentKeyHash, Callback memory callback,
bytes32 requestId, uint256 randomness) {
// blockNum follows proof, which follows length word (only direct-number
// constants are allowed in assembly, so have to compute this in code)
uint256 BLOCKNUM_OFFSET = 0x20 + PROOF_LENGTH;
// _proof.length skips the initial length word, so not including the
// blocknum in this length check balances out.
require(_proof.length == BLOCKNUM_OFFSET, "wrong proof length");
uint256[2] memory publicKey;
uint256 preSeed;
uint256 blockNum;
assembly { // solhint-disable-line no-inline-assembly
publicKey := add(_proof, PUBLIC_KEY_OFFSET)
preSeed := mload(add(_proof, PRESEED_OFFSET))
blockNum := mload(add(_proof, BLOCKNUM_OFFSET))
}
currentKeyHash = hashOfKey(publicKey);
requestId = makeRequestId(currentKeyHash, preSeed);
callback = callbacks[requestId];
require(callback.callbackContract != address(0), "no corresponding request");
require(callback.seedAndBlockNum == keccak256(abi.encodePacked(preSeed,
blockNum)), "wrong preSeed or block num");
bytes32 blockHash = blockhash(blockNum);
if (blockHash == bytes32(0)) {
blockHash = blockHashStore.getBlockhash(blockNum);
require(blockHash != bytes32(0), "please prove blockhash");
}
// The seed actually used by the VRF machinery, mixing in the blockhash
uint256 actualSeed = uint256(keccak256(abi.encodePacked(preSeed, blockHash)));
// solhint-disable-next-line no-inline-assembly
assembly { // Construct the actual proof from the remains of _proof
mstore(add(_proof, PRESEED_OFFSET), actualSeed)
mstore(_proof, PROOF_LENGTH)
}
randomness = VRF.randomValueFromVRFProof(_proof); // Reverts on failure
}
/**
* @dev Allows the oracle operator to withdraw their LINK
* @param _recipient is the address the funds will be sent to
* @param _amount is the amount of LINK transferred from the Coordinator contract
*/
function withdraw(address _recipient, uint256 _amount)
external
hasAvailableFunds(_amount)
{
withdrawableTokens[msg.sender] = withdrawableTokens[msg.sender].sub(_amount);
token.approve(address(victorMid), _amount);
assert(victorMid.transferFrom(address(this), _recipient, _amount));
}
/**
* @notice Returns the serviceAgreements key associated with this public key
* @param _publicKey the key to return the address for
*/
function hashOfKey(uint256[2] memory _publicKey) public pure returns (bytes32) {
return keccak256(abi.encodePacked(_publicKey));
}
function hashOfKeyBytes(bytes memory _publicKey) public pure returns (bytes32) {
return keccak256(_publicKey);
}
/**
* @dev Reverts if amount is not at least what was agreed upon in the service agreement
* @param _feePaid The payment for the request
* @param _keyHash The key which the request is for
*/
modifier sufficientVCT(uint256 _feePaid, bytes32 _keyHash) {
require(_feePaid >= serviceAgreements[_keyHash].fee, "Below agreed payment");
_;
}
/**
* @dev Reverts if not sent from the LINK token
*/
modifier onlyVictorMid() {
require(msg.sender == address(victorMid), "Must use victorMid token");
_;
}
/**
* @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[msg.sender] >= _amount, "can't withdraw more than balance");
_;
}
/**
* @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");
_;
}
/**
* @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-line no-inline-assembly
funcSelector := mload(add(_data, 32))
}
require(funcSelector == this.vrfRequest.selector, "Must use whitelisted functions");
_;
}
/**
* @dev Reverts if the callback address is the LINK token
* @param _to The callback address
*/
modifier checkCallbackAddress(address _to) {
require(_to != address(victorMid), "Cannot callback to LINK");
_;
}
}