@@ -6,6 +6,7 @@ import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
6
6
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol " ;
7
7
import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol " ;
8
8
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol " ;
9
+ import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol " ;
9
10
import "@openzeppelin/contracts/utils/math/SafeCast.sol " ;
10
11
import "./EntropyState.sol " ;
11
12
@@ -163,29 +164,23 @@ abstract contract Entropy is IEntropy, EntropyState {
163
164
require (sent, "withdrawal to msg.sender failed " );
164
165
}
165
166
166
- // As a user, request a random number from `provider`. Prior to calling this method, the user should
167
- // generate a random number x and keep it secret. The user should then compute hash(x) and pass that
168
- // as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
169
- //
170
- // This method returns a sequence number. The user should pass this sequence number to
171
- // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
172
- // number. The user should then call fulfillRequest to construct the final random number.
173
- //
174
- // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
175
- // Note that excess value is *not* refunded to the caller.
176
- function request (
167
+ // requestHelper allocates and returns a new request for the given provider.
168
+ // Note: This method will revert unless the caller provides a sufficient fee
169
+ // (at least getFee(provider)) as msg.value.
170
+ function requestHelper (
177
171
address provider ,
178
172
bytes32 userCommitment ,
179
- bool useBlockHash
180
- ) public payable override returns (uint64 assignedSequenceNumber ) {
173
+ bool useBlockhash ,
174
+ bool isRequestWithCallback
175
+ ) internal returns (EntropyStructs.Request storage req ) {
181
176
EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
182
177
provider
183
178
];
184
179
if (_state.providers[provider].sequenceNumber == 0 )
185
180
revert EntropyErrors.NoSuchProvider ();
186
181
187
182
// Assign a sequence number to the request
188
- assignedSequenceNumber = providerInfo.sequenceNumber;
183
+ uint64 assignedSequenceNumber = providerInfo.sequenceNumber;
189
184
if (assignedSequenceNumber >= providerInfo.endSequenceNumber)
190
185
revert EntropyErrors.OutOfRandomness ();
191
186
providerInfo.sequenceNumber += 1 ;
@@ -200,10 +195,7 @@ abstract contract Entropy is IEntropy, EntropyState {
200
195
// Store the user's commitment so that we can fulfill the request later.
201
196
// Warning: this code needs to overwrite *every* field in the request, because the returned request can be
202
197
// filled with arbitrary data.
203
- EntropyStructs.Request storage req = allocRequest (
204
- provider,
205
- assignedSequenceNumber
206
- );
198
+ req = allocRequest (provider, assignedSequenceNumber);
207
199
req.provider = provider;
208
200
req.sequenceNumber = assignedSequenceNumber;
209
201
req.numHashes = SafeCast.toUint32 (
@@ -216,51 +208,87 @@ abstract contract Entropy is IEntropy, EntropyState {
216
208
req.requester = msg .sender ;
217
209
218
210
req.blockNumber = SafeCast.toUint64 (block .number );
219
- req.useBlockhash = useBlockHash;
211
+ req.useBlockhash = useBlockhash;
212
+ req.isRequestWithCallback = isRequestWithCallback;
213
+ }
220
214
215
+ // As a user, request a random number from `provider`. Prior to calling this method, the user should
216
+ // generate a random number x and keep it secret. The user should then compute hash(x) and pass that
217
+ // as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
218
+ //
219
+ // This method returns a sequence number. The user should pass this sequence number to
220
+ // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
221
+ // number. The user should then call fulfillRequest to construct the final random number.
222
+ //
223
+ // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
224
+ // Note that excess value is *not* refunded to the caller.
225
+ function request (
226
+ address provider ,
227
+ bytes32 userCommitment ,
228
+ bool useBlockHash
229
+ ) public payable override returns (uint64 assignedSequenceNumber ) {
230
+ EntropyStructs.Request storage req = requestHelper (
231
+ provider,
232
+ userCommitment,
233
+ useBlockHash,
234
+ false
235
+ );
236
+ assignedSequenceNumber = req.sequenceNumber;
221
237
emit Requested (req);
222
238
}
223
239
224
- // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
225
- // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
226
- // the corresponding random number.
240
+ // Request a random number. The method expects the provider address and a secret random number
241
+ // in the arguments. It returns a sequence number.
227
242
//
228
- // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
229
- // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
230
- // If you need to use the returned random number more than once, you are responsible for storing it.
243
+ // The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
244
+ // The `entropyCallback` method on that interface will receive a callback with the generated random number.
231
245
//
232
- // This function must be called by the same `msg.sender` that originally requested the random number. This check
233
- // prevents denial-of-service attacks where another actor front-runs the requester's reveal transaction .
234
- function reveal (
246
+ // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
247
+ // Note that excess value is *not* refunded to the caller .
248
+ function requestWithCallback (
235
249
address provider ,
236
- uint64 sequenceNumber ,
237
- bytes32 userRandomness ,
238
- bytes32 providerRevelation
239
- ) public override returns (bytes32 randomNumber ) {
240
- EntropyStructs.Request storage req = findRequest (
250
+ bytes32 userRandomNumber
251
+ ) public payable override returns (uint64 ) {
252
+ EntropyStructs.Request storage req = requestHelper (
241
253
provider,
242
- sequenceNumber
254
+ constructUserCommitment (userRandomNumber),
255
+ // If useBlockHash is set to true, it allows a scenario in which the provider and miner can collude.
256
+ // If we remove the blockHash from this, the provider would have no choice but to provide its committed
257
+ // random number. Hence, useBlockHash is set to false.
258
+ false ,
259
+ true
260
+ );
261
+
262
+ emit RequestedWithCallback (
263
+ provider,
264
+ req.requester,
265
+ req.sequenceNumber,
266
+ userRandomNumber,
267
+ req
243
268
);
244
- // Check that there is an active request for the given provider / sequence number.
245
- if (
246
- req.sequenceNumber == 0 ||
247
- req.provider != provider ||
248
- req.sequenceNumber != sequenceNumber
249
- ) revert EntropyErrors.NoSuchRequest ();
250
269
251
- if (req.requester != msg .sender ) revert EntropyErrors.Unauthorized ();
270
+ return req.sequenceNumber;
271
+ }
252
272
273
+ // This method validates the provided user's revelation and provider's revelation against the corresponding
274
+ // commitment in the in-flight request. If both values are validated, this method will update the provider
275
+ // current commitment and returns the generated random number.
276
+ function revealHelper (
277
+ EntropyStructs.Request storage req ,
278
+ bytes32 userRevelation ,
279
+ bytes32 providerRevelation
280
+ ) internal returns (bytes32 randomNumber , bytes32 blockHash ) {
253
281
bytes32 providerCommitment = constructProviderCommitment (
254
282
req.numHashes,
255
283
providerRevelation
256
284
);
257
- bytes32 userCommitment = constructUserCommitment (userRandomness );
285
+ bytes32 userCommitment = constructUserCommitment (userRevelation );
258
286
if (
259
287
keccak256 (bytes .concat (userCommitment, providerCommitment)) !=
260
288
req.commitment
261
289
) revert EntropyErrors.IncorrectRevelation ();
262
290
263
- bytes32 blockHash = bytes32 (uint256 (0 ));
291
+ blockHash = bytes32 (uint256 (0 ));
264
292
if (req.useBlockhash) {
265
293
bytes32 _blockHash = blockhash (req.blockNumber);
266
294
@@ -277,28 +305,110 @@ abstract contract Entropy is IEntropy, EntropyState {
277
305
}
278
306
279
307
randomNumber = combineRandomValues (
280
- userRandomness ,
308
+ userRevelation ,
281
309
providerRevelation,
282
310
blockHash
283
311
);
284
312
313
+ EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
314
+ req.provider
315
+ ];
316
+ if (providerInfo.currentCommitmentSequenceNumber < req.sequenceNumber) {
317
+ providerInfo.currentCommitmentSequenceNumber = req.sequenceNumber;
318
+ providerInfo.currentCommitment = providerRevelation;
319
+ }
320
+ }
321
+
322
+ // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
323
+ // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
324
+ // the corresponding random number.
325
+ //
326
+ // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
327
+ // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
328
+ // If you need to use the returned random number more than once, you are responsible for storing it.
329
+ //
330
+ // This function must be called by the same `msg.sender` that originally requested the random number. This check
331
+ // prevents denial-of-service attacks where another actor front-runs the requester's reveal transaction.
332
+ function reveal (
333
+ address provider ,
334
+ uint64 sequenceNumber ,
335
+ bytes32 userRevelation ,
336
+ bytes32 providerRevelation
337
+ ) public override returns (bytes32 randomNumber ) {
338
+ EntropyStructs.Request storage req = findActiveRequest (
339
+ provider,
340
+ sequenceNumber
341
+ );
342
+
343
+ if (req.isRequestWithCallback) {
344
+ revert EntropyErrors.InvalidRevealCall ();
345
+ }
346
+
347
+ if (req.requester != msg .sender ) {
348
+ revert EntropyErrors.Unauthorized ();
349
+ }
350
+ bytes32 blockHash;
351
+ (randomNumber, blockHash) = revealHelper (
352
+ req,
353
+ userRevelation,
354
+ providerRevelation
355
+ );
285
356
emit Revealed (
286
357
req,
287
- userRandomness ,
358
+ userRevelation ,
288
359
providerRevelation,
289
360
blockHash,
290
361
randomNumber
291
362
);
292
-
293
363
clearRequest (provider, sequenceNumber);
364
+ }
294
365
295
- EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
296
- provider
297
- ];
298
- if (providerInfo.currentCommitmentSequenceNumber < sequenceNumber) {
299
- providerInfo.currentCommitmentSequenceNumber = sequenceNumber;
300
- providerInfo.currentCommitment = providerRevelation;
366
+ // Fulfill a request for a random number and call back the requester. This method validates the provided userRandomness
367
+ // and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated,
368
+ // this function calls the requester's entropyCallback method with the sequence number and the random number as arguments.
369
+ //
370
+ // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
371
+ // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
372
+ // If you need to use the returned random number more than once, you are responsible for storing it.
373
+ //
374
+ // Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
375
+ function revealWithCallback (
376
+ address provider ,
377
+ uint64 sequenceNumber ,
378
+ bytes32 userRandomNumber ,
379
+ bytes32 providerRevelation
380
+ ) public override {
381
+ EntropyStructs.Request storage req = findActiveRequest (
382
+ provider,
383
+ sequenceNumber
384
+ );
385
+
386
+ if (! req.isRequestWithCallback) {
387
+ revert EntropyErrors.InvalidRevealCall ();
301
388
}
389
+ bytes32 blockHash;
390
+ bytes32 randomNumber;
391
+ (randomNumber, blockHash) = revealHelper (
392
+ req,
393
+ userRandomNumber,
394
+ providerRevelation
395
+ );
396
+
397
+ address callAddress = req.requester;
398
+
399
+ emit RevealedWithCallback (
400
+ req,
401
+ userRandomNumber,
402
+ providerRevelation,
403
+ randomNumber
404
+ );
405
+
406
+ clearRequest (provider, sequenceNumber);
407
+
408
+ IEntropyConsumer (callAddress)._entropyCallback (
409
+ sequenceNumber,
410
+ randomNumber
411
+ );
302
412
}
303
413
304
414
function getProviderInfo (
@@ -408,6 +518,23 @@ abstract contract Entropy is IEntropy, EntropyState {
408
518
}
409
519
}
410
520
521
+ // Find an in-flight active request for given the provider and the sequence number.
522
+ // This method returns a reference to the request, and will revert if the request is
523
+ // not active.
524
+ function findActiveRequest (
525
+ address provider ,
526
+ uint64 sequenceNumber
527
+ ) internal view returns (EntropyStructs.Request storage req ) {
528
+ req = findRequest (provider, sequenceNumber);
529
+
530
+ // Check there is an active request for the given provider and sequence number.
531
+ if (
532
+ ! isActive (req) ||
533
+ req.provider != provider ||
534
+ req.sequenceNumber != sequenceNumber
535
+ ) revert EntropyErrors.NoSuchRequest ();
536
+ }
537
+
411
538
// Find an in-flight request.
412
539
// Note that this method can return requests that are not currently active. The caller is responsible for checking
413
540
// that the returned request is active (if they care).
0 commit comments