Skip to content

Commit e7bf47a

Browse files
author
Dev Kalra
authored
feat(entropy-v2): request with callback (#1342)
* request with callback * address comments * pre-commit * compilation successful * pre-commit * add tests * generate-abis * pre-commit * correct version * address comments * pre-commit * remove unused * add comments * pre-commit * gen abi * naming consistency * remove gas limit comment * requestWithCallback comment * remove unnecessary asserts * pre commit * update request with callback coment * abis regen * refactor as per feedback * abi gen * rename * implement ientropyconsumer * gen abi * comment entropy consumer * test fix * add comment * reintroduce blockhash * add error for invalid reveal call * use getEntropy in entropy consumer * add test for requestAndRevealWithCallback * pass through for entropy consumer * pre commit fix * abi gen * address comments * address feedback * gen abis * pre commit run
1 parent 926aa55 commit e7bf47a

File tree

13 files changed

+879
-60
lines changed

13 files changed

+879
-60
lines changed

target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

Lines changed: 179 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
66
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
77
import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol";
88
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
9+
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
910
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
1011
import "./EntropyState.sol";
1112

@@ -163,29 +164,23 @@ abstract contract Entropy is IEntropy, EntropyState {
163164
require(sent, "withdrawal to msg.sender failed");
164165
}
165166

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(
177171
address provider,
178172
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) {
181176
EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
182177
provider
183178
];
184179
if (_state.providers[provider].sequenceNumber == 0)
185180
revert EntropyErrors.NoSuchProvider();
186181

187182
// Assign a sequence number to the request
188-
assignedSequenceNumber = providerInfo.sequenceNumber;
183+
uint64 assignedSequenceNumber = providerInfo.sequenceNumber;
189184
if (assignedSequenceNumber >= providerInfo.endSequenceNumber)
190185
revert EntropyErrors.OutOfRandomness();
191186
providerInfo.sequenceNumber += 1;
@@ -200,10 +195,7 @@ abstract contract Entropy is IEntropy, EntropyState {
200195
// Store the user's commitment so that we can fulfill the request later.
201196
// Warning: this code needs to overwrite *every* field in the request, because the returned request can be
202197
// filled with arbitrary data.
203-
EntropyStructs.Request storage req = allocRequest(
204-
provider,
205-
assignedSequenceNumber
206-
);
198+
req = allocRequest(provider, assignedSequenceNumber);
207199
req.provider = provider;
208200
req.sequenceNumber = assignedSequenceNumber;
209201
req.numHashes = SafeCast.toUint32(
@@ -216,51 +208,87 @@ abstract contract Entropy is IEntropy, EntropyState {
216208
req.requester = msg.sender;
217209

218210
req.blockNumber = SafeCast.toUint64(block.number);
219-
req.useBlockhash = useBlockHash;
211+
req.useBlockhash = useBlockhash;
212+
req.isRequestWithCallback = isRequestWithCallback;
213+
}
220214

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;
221237
emit Requested(req);
222238
}
223239

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.
227242
//
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.
231245
//
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(
235249
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(
241253
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
243268
);
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();
250269

251-
if (req.requester != msg.sender) revert EntropyErrors.Unauthorized();
270+
return req.sequenceNumber;
271+
}
252272

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) {
253281
bytes32 providerCommitment = constructProviderCommitment(
254282
req.numHashes,
255283
providerRevelation
256284
);
257-
bytes32 userCommitment = constructUserCommitment(userRandomness);
285+
bytes32 userCommitment = constructUserCommitment(userRevelation);
258286
if (
259287
keccak256(bytes.concat(userCommitment, providerCommitment)) !=
260288
req.commitment
261289
) revert EntropyErrors.IncorrectRevelation();
262290

263-
bytes32 blockHash = bytes32(uint256(0));
291+
blockHash = bytes32(uint256(0));
264292
if (req.useBlockhash) {
265293
bytes32 _blockHash = blockhash(req.blockNumber);
266294

@@ -277,28 +305,110 @@ abstract contract Entropy is IEntropy, EntropyState {
277305
}
278306

279307
randomNumber = combineRandomValues(
280-
userRandomness,
308+
userRevelation,
281309
providerRevelation,
282310
blockHash
283311
);
284312

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+
);
285356
emit Revealed(
286357
req,
287-
userRandomness,
358+
userRevelation,
288359
providerRevelation,
289360
blockHash,
290361
randomNumber
291362
);
292-
293363
clearRequest(provider, sequenceNumber);
364+
}
294365

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();
301388
}
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+
);
302412
}
303413

304414
function getProviderInfo(
@@ -408,6 +518,23 @@ abstract contract Entropy is IEntropy, EntropyState {
408518
}
409519
}
410520

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+
411538
// Find an in-flight request.
412539
// Note that this method can return requests that are not currently active. The caller is responsible for checking
413540
// that the returned request is active (if they care).

target_chains/ethereum/contracts/contracts/entropy/EntropyUpgradable.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,6 @@ contract EntropyUpgradable is
105105
}
106106

107107
function version() public pure returns (string memory) {
108-
return "0.1.0";
108+
return "0.2.0";
109109
}
110110
}

0 commit comments

Comments
 (0)