Эх сурвалжийг харах

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
Dev Kalra 1 жил өмнө
parent
commit
e7bf47a18e

+ 179 - 52
target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

@@ -6,6 +6,7 @@ import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
 import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
 import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol";
 import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
+import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
 import "@openzeppelin/contracts/utils/math/SafeCast.sol";
 import "./EntropyState.sol";
 
@@ -163,21 +164,15 @@ abstract contract Entropy is IEntropy, EntropyState {
         require(sent, "withdrawal to msg.sender failed");
     }
 
-    // As a user, request a random number from `provider`. Prior to calling this method, the user should
-    // generate a random number x and keep it secret. The user should then compute hash(x) and pass that
-    // as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
-    //
-    // This method returns a sequence number. The user should pass this sequence number to
-    // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
-    // number. The user should then call fulfillRequest to construct the final random number.
-    //
-    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
-    // Note that excess value is *not* refunded to the caller.
-    function request(
+    // requestHelper allocates and returns a new request for the given provider.
+    // Note: This method will revert unless the caller provides a sufficient fee
+    // (at least getFee(provider)) as msg.value.
+    function requestHelper(
         address provider,
         bytes32 userCommitment,
-        bool useBlockHash
-    ) public payable override returns (uint64 assignedSequenceNumber) {
+        bool useBlockhash,
+        bool isRequestWithCallback
+    ) internal returns (EntropyStructs.Request storage req) {
         EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
             provider
         ];
@@ -185,7 +180,7 @@ abstract contract Entropy is IEntropy, EntropyState {
             revert EntropyErrors.NoSuchProvider();
 
         // Assign a sequence number to the request
-        assignedSequenceNumber = providerInfo.sequenceNumber;
+        uint64 assignedSequenceNumber = providerInfo.sequenceNumber;
         if (assignedSequenceNumber >= providerInfo.endSequenceNumber)
             revert EntropyErrors.OutOfRandomness();
         providerInfo.sequenceNumber += 1;
@@ -200,10 +195,7 @@ abstract contract Entropy is IEntropy, EntropyState {
         // Store the user's commitment so that we can fulfill the request later.
         // Warning: this code needs to overwrite *every* field in the request, because the returned request can be
         // filled with arbitrary data.
-        EntropyStructs.Request storage req = allocRequest(
-            provider,
-            assignedSequenceNumber
-        );
+        req = allocRequest(provider, assignedSequenceNumber);
         req.provider = provider;
         req.sequenceNumber = assignedSequenceNumber;
         req.numHashes = SafeCast.toUint32(
@@ -216,51 +208,87 @@ abstract contract Entropy is IEntropy, EntropyState {
         req.requester = msg.sender;
 
         req.blockNumber = SafeCast.toUint64(block.number);
-        req.useBlockhash = useBlockHash;
+        req.useBlockhash = useBlockhash;
+        req.isRequestWithCallback = isRequestWithCallback;
+    }
 
+    // As a user, request a random number from `provider`. Prior to calling this method, the user should
+    // generate a random number x and keep it secret. The user should then compute hash(x) and pass that
+    // as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
+    //
+    // This method returns a sequence number. The user should pass this sequence number to
+    // their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
+    // number. The user should then call fulfillRequest to construct the final random number.
+    //
+    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
+    // Note that excess value is *not* refunded to the caller.
+    function request(
+        address provider,
+        bytes32 userCommitment,
+        bool useBlockHash
+    ) public payable override returns (uint64 assignedSequenceNumber) {
+        EntropyStructs.Request storage req = requestHelper(
+            provider,
+            userCommitment,
+            useBlockHash,
+            false
+        );
+        assignedSequenceNumber = req.sequenceNumber;
         emit Requested(req);
     }
 
-    // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
-    // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
-    // the corresponding random number.
+    // Request a random number. The method expects the provider address and a secret random number
+    // in the arguments. It returns a sequence number.
     //
-    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
-    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
-    // If you need to use the returned random number more than once, you are responsible for storing it.
+    // The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
+    // The `entropyCallback` method on that interface will receive a callback with the generated random number.
     //
-    // This function must be called by the same `msg.sender` that originally requested the random number. This check
-    // prevents denial-of-service attacks where another actor front-runs the requester's reveal transaction.
-    function reveal(
+    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
+    // Note that excess value is *not* refunded to the caller.
+    function requestWithCallback(
         address provider,
-        uint64 sequenceNumber,
-        bytes32 userRandomness,
-        bytes32 providerRevelation
-    ) public override returns (bytes32 randomNumber) {
-        EntropyStructs.Request storage req = findRequest(
+        bytes32 userRandomNumber
+    ) public payable override returns (uint64) {
+        EntropyStructs.Request storage req = requestHelper(
             provider,
-            sequenceNumber
+            constructUserCommitment(userRandomNumber),
+            // If useBlockHash is set to true, it allows a scenario in which the provider and miner can collude.
+            // If we remove the blockHash from this, the provider would have no choice but to provide its committed
+            // random number. Hence, useBlockHash is set to false.
+            false,
+            true
+        );
+
+        emit RequestedWithCallback(
+            provider,
+            req.requester,
+            req.sequenceNumber,
+            userRandomNumber,
+            req
         );
-        // Check that there is an active request for the given provider / sequence number.
-        if (
-            req.sequenceNumber == 0 ||
-            req.provider != provider ||
-            req.sequenceNumber != sequenceNumber
-        ) revert EntropyErrors.NoSuchRequest();
 
-        if (req.requester != msg.sender) revert EntropyErrors.Unauthorized();
+        return req.sequenceNumber;
+    }
 
+    // This method validates the provided user's revelation and provider's revelation against the corresponding
+    // commitment in the in-flight request. If both values are validated, this method will update the provider
+    // current commitment and returns the generated random number.
+    function revealHelper(
+        EntropyStructs.Request storage req,
+        bytes32 userRevelation,
+        bytes32 providerRevelation
+    ) internal returns (bytes32 randomNumber, bytes32 blockHash) {
         bytes32 providerCommitment = constructProviderCommitment(
             req.numHashes,
             providerRevelation
         );
-        bytes32 userCommitment = constructUserCommitment(userRandomness);
+        bytes32 userCommitment = constructUserCommitment(userRevelation);
         if (
             keccak256(bytes.concat(userCommitment, providerCommitment)) !=
             req.commitment
         ) revert EntropyErrors.IncorrectRevelation();
 
-        bytes32 blockHash = bytes32(uint256(0));
+        blockHash = bytes32(uint256(0));
         if (req.useBlockhash) {
             bytes32 _blockHash = blockhash(req.blockNumber);
 
@@ -277,28 +305,110 @@ abstract contract Entropy is IEntropy, EntropyState {
         }
 
         randomNumber = combineRandomValues(
-            userRandomness,
+            userRevelation,
             providerRevelation,
             blockHash
         );
 
+        EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
+            req.provider
+        ];
+        if (providerInfo.currentCommitmentSequenceNumber < req.sequenceNumber) {
+            providerInfo.currentCommitmentSequenceNumber = req.sequenceNumber;
+            providerInfo.currentCommitment = providerRevelation;
+        }
+    }
+
+    // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
+    // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
+    // the corresponding random number.
+    //
+    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
+    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
+    // If you need to use the returned random number more than once, you are responsible for storing it.
+    //
+    // This function must be called by the same `msg.sender` that originally requested the random number. This check
+    // prevents denial-of-service attacks where another actor front-runs the requester's reveal transaction.
+    function reveal(
+        address provider,
+        uint64 sequenceNumber,
+        bytes32 userRevelation,
+        bytes32 providerRevelation
+    ) public override returns (bytes32 randomNumber) {
+        EntropyStructs.Request storage req = findActiveRequest(
+            provider,
+            sequenceNumber
+        );
+
+        if (req.isRequestWithCallback) {
+            revert EntropyErrors.InvalidRevealCall();
+        }
+
+        if (req.requester != msg.sender) {
+            revert EntropyErrors.Unauthorized();
+        }
+        bytes32 blockHash;
+        (randomNumber, blockHash) = revealHelper(
+            req,
+            userRevelation,
+            providerRevelation
+        );
         emit Revealed(
             req,
-            userRandomness,
+            userRevelation,
             providerRevelation,
             blockHash,
             randomNumber
         );
-
         clearRequest(provider, sequenceNumber);
+    }
 
-        EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
-            provider
-        ];
-        if (providerInfo.currentCommitmentSequenceNumber < sequenceNumber) {
-            providerInfo.currentCommitmentSequenceNumber = sequenceNumber;
-            providerInfo.currentCommitment = providerRevelation;
+    // Fulfill a request for a random number and call back the requester. This method validates the provided userRandomness
+    // and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated,
+    // this function calls the requester's entropyCallback method with the sequence number and the random number as arguments.
+    //
+    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
+    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
+    // If you need to use the returned random number more than once, you are responsible for storing it.
+    //
+    // Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
+    function revealWithCallback(
+        address provider,
+        uint64 sequenceNumber,
+        bytes32 userRandomNumber,
+        bytes32 providerRevelation
+    ) public override {
+        EntropyStructs.Request storage req = findActiveRequest(
+            provider,
+            sequenceNumber
+        );
+
+        if (!req.isRequestWithCallback) {
+            revert EntropyErrors.InvalidRevealCall();
         }
+        bytes32 blockHash;
+        bytes32 randomNumber;
+        (randomNumber, blockHash) = revealHelper(
+            req,
+            userRandomNumber,
+            providerRevelation
+        );
+
+        address callAddress = req.requester;
+
+        emit RevealedWithCallback(
+            req,
+            userRandomNumber,
+            providerRevelation,
+            randomNumber
+        );
+
+        clearRequest(provider, sequenceNumber);
+
+        IEntropyConsumer(callAddress)._entropyCallback(
+            sequenceNumber,
+            randomNumber
+        );
     }
 
     function getProviderInfo(
@@ -408,6 +518,23 @@ abstract contract Entropy is IEntropy, EntropyState {
         }
     }
 
+    // Find an in-flight active request for given the provider and the sequence number.
+    // This method returns a reference to the request, and will revert if the request is
+    // not active.
+    function findActiveRequest(
+        address provider,
+        uint64 sequenceNumber
+    ) internal view returns (EntropyStructs.Request storage req) {
+        req = findRequest(provider, sequenceNumber);
+
+        // Check there is an active request for the given provider and sequence number.
+        if (
+            !isActive(req) ||
+            req.provider != provider ||
+            req.sequenceNumber != sequenceNumber
+        ) revert EntropyErrors.NoSuchRequest();
+    }
+
     // Find an in-flight request.
     // Note that this method can return requests that are not currently active. The caller is responsible for checking
     // that the returned request is active (if they care).

+ 1 - 1
target_chains/ethereum/contracts/contracts/entropy/EntropyUpgradable.sol

@@ -105,6 +105,6 @@ contract EntropyUpgradable is
     }
 
     function version() public pure returns (string memory) {
-        return "0.1.0";
+        return "0.2.0";
     }
 }

+ 211 - 1
target_chains/ethereum/contracts/forge-test/Entropy.t.sol

@@ -4,13 +4,16 @@ pragma solidity ^0.8.0;
 
 import "forge-std/Test.sol";
 import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
+import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol";
+import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
+import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
 import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
 import "./utils/EntropyTestUtils.t.sol";
 import "../contracts/entropy/EntropyUpgradable.sol";
 
 // TODO
 // - fuzz test?
-contract EntropyTest is Test, EntropyTestUtils {
+contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
     ERC1967Proxy public proxy;
     EntropyUpgradable public random;
 
@@ -222,6 +225,12 @@ contract EntropyTest is Test, EntropyTestUtils {
             ALL_ZEROS
         );
 
+        EntropyStructs.Request memory reqAfterReveal = random.getRequest(
+            provider1,
+            sequenceNumber
+        );
+        assertEq(reqAfterReveal.sequenceNumber, 0);
+
         // You can only reveal the random number once. This isn't a feature of the contract per se, but it is
         // the expected behavior.
         assertRevealReverts(
@@ -729,4 +738,205 @@ contract EntropyTest is Test, EntropyTestUtils {
         vm.expectRevert();
         random.setProviderUri(newUri);
     }
+
+    function testRequestWithCallbackAndReveal() public {
+        bytes32 userRandomNumber = bytes32(uint(42));
+        uint fee = random.getFee(provider1);
+        EntropyStructs.ProviderInfo memory providerInfo = random
+            .getProviderInfo(provider1);
+
+        vm.roll(1234);
+        vm.deal(user1, fee);
+        vm.startPrank(user1);
+        vm.expectEmit(false, false, false, true, address(random));
+        emit RequestedWithCallback(
+            provider1,
+            user1,
+            providerInfo.sequenceNumber,
+            userRandomNumber,
+            EntropyStructs.Request({
+                provider: provider1,
+                sequenceNumber: providerInfo.sequenceNumber,
+                numHashes: SafeCast.toUint32(
+                    providerInfo.sequenceNumber -
+                        providerInfo.currentCommitmentSequenceNumber
+                ),
+                commitment: keccak256(
+                    bytes.concat(
+                        random.constructUserCommitment(userRandomNumber),
+                        providerInfo.currentCommitment
+                    )
+                ),
+                blockNumber: 1234,
+                requester: user1,
+                useBlockhash: false,
+                isRequestWithCallback: true
+            })
+        );
+        vm.roll(1234);
+        uint64 assignedSequenceNumber = random.requestWithCallback{value: fee}(
+            provider1,
+            userRandomNumber
+        );
+
+        assertEq(
+            random.getRequest(provider1, assignedSequenceNumber).requester,
+            user1
+        );
+
+        assertEq(
+            random.getRequest(provider1, assignedSequenceNumber).provider,
+            provider1
+        );
+
+        vm.expectRevert(EntropyErrors.InvalidRevealCall.selector);
+        random.reveal(
+            provider1,
+            assignedSequenceNumber,
+            userRandomNumber,
+            provider1Proofs[assignedSequenceNumber]
+        );
+        vm.stopPrank();
+    }
+
+    function testRequestWithCallbackAndRevealWithCallback() public {
+        bytes32 userRandomNumber = bytes32(uint(42));
+        uint fee = random.getFee(provider1);
+        EntropyConsumer consumer = new EntropyConsumer(address(random));
+        vm.deal(user1, fee);
+        vm.prank(user1);
+        uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}(
+            userRandomNumber
+        );
+        EntropyStructs.Request memory req = random.getRequest(
+            provider1,
+            assignedSequenceNumber
+        );
+        bytes32 blockHash = bytes32(uint256(0));
+
+        vm.expectEmit(false, false, false, true, address(random));
+        emit RevealedWithCallback(
+            req,
+            userRandomNumber,
+            provider1Proofs[assignedSequenceNumber],
+            random.combineRandomValues(
+                userRandomNumber,
+                provider1Proofs[assignedSequenceNumber],
+                0
+            )
+        );
+        vm.prank(user1);
+        random.revealWithCallback(
+            provider1,
+            assignedSequenceNumber,
+            userRandomNumber,
+            provider1Proofs[assignedSequenceNumber]
+        );
+
+        assertEq(consumer.sequence(), assignedSequenceNumber);
+        assertEq(
+            consumer.randomness(),
+            random.combineRandomValues(
+                userRandomNumber,
+                provider1Proofs[assignedSequenceNumber],
+                // No blockhash is being used in callback method. As it
+                // is being depreceated. Passing 0 for it.
+                0
+            )
+        );
+
+        EntropyStructs.Request memory reqAfterReveal = random.getRequest(
+            provider1,
+            assignedSequenceNumber
+        );
+        assertEq(reqAfterReveal.sequenceNumber, 0);
+    }
+
+    function testRequestAndRevealWithCallback() public {
+        uint64 sequenceNumber = request(user2, provider1, 42, false);
+        assertEq(random.getRequest(provider1, sequenceNumber).requester, user2);
+
+        vm.expectRevert(EntropyErrors.InvalidRevealCall.selector);
+        vm.prank(user2);
+        random.revealWithCallback(
+            provider1,
+            sequenceNumber,
+            bytes32(uint256(42)),
+            provider1Proofs[sequenceNumber]
+        );
+    }
+
+    function testRequestWithCallbackAndRevealWithCallbackFailing() public {
+        bytes32 userRandomNumber = bytes32(uint(42));
+        uint fee = random.getFee(provider1);
+        EntropyConsumerFails consumer = new EntropyConsumerFails(
+            address(random)
+        );
+        vm.deal(address(consumer), fee);
+        vm.startPrank(address(consumer));
+        uint64 assignedSequenceNumber = random.requestWithCallback{value: fee}(
+            provider1,
+            userRandomNumber
+        );
+
+        vm.expectRevert();
+        random.revealWithCallback(
+            provider1,
+            assignedSequenceNumber,
+            userRandomNumber,
+            provider1Proofs[assignedSequenceNumber]
+        );
+    }
+}
+
+contract EntropyConsumer is IEntropyConsumer {
+    uint64 public sequence;
+    bytes32 public randomness;
+    address public entropy;
+
+    constructor(address _entropy) {
+        entropy = _entropy;
+    }
+
+    function requestEntropy(
+        bytes32 randomNumber
+    ) public payable returns (uint64 sequenceNumber) {
+        address provider = IEntropy(entropy).getDefaultProvider();
+        sequenceNumber = IEntropy(entropy).requestWithCallback{
+            value: msg.value
+        }(provider, randomNumber);
+    }
+
+    function getEntropy() internal view override returns (address) {
+        return entropy;
+    }
+
+    function entropyCallback(
+        uint64 _sequence,
+        bytes32 _randomness
+    ) internal override {
+        sequence = _sequence;
+        randomness = _randomness;
+    }
+}
+
+contract EntropyConsumerFails is IEntropyConsumer {
+    uint64 public sequence;
+    bytes32 public randomness;
+    address public entropy;
+
+    constructor(address _entropy) {
+        entropy = _entropy;
+    }
+
+    function getEntropy() internal view override returns (address) {
+        return entropy;
+    }
+
+    function entropyCallback(
+        uint64 _sequence,
+        bytes32 _randomness
+    ) internal override {
+        revert("Callback failed");
+    }
 }

+ 4 - 0
target_chains/ethereum/entropy_sdk/solidity/EntropyErrors.sol

@@ -34,4 +34,8 @@ library EntropyErrors {
     // The blockhash is 0.
     // Signature: 0x92555c0e
     error BlockhashUnavailable();
+    // if a request was made using `requestWithCallback`, request should be fulfilled using `revealWithCallback`
+    // else if a request was made using `request`, request should be fulfilled using `reveal`
+    // Signature: 0x50f0dc92
+    error InvalidRevealCall();
 }

+ 13 - 0
target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol

@@ -7,6 +7,13 @@ interface EntropyEvents {
     event Registered(EntropyStructs.ProviderInfo provider);
 
     event Requested(EntropyStructs.Request request);
+    event RequestedWithCallback(
+        address indexed provider,
+        address indexed requestor,
+        uint64 indexed sequenceNumber,
+        bytes32 userRandomNumber,
+        EntropyStructs.Request request
+    );
 
     event Revealed(
         EntropyStructs.Request request,
@@ -15,6 +22,12 @@ interface EntropyEvents {
         bytes32 blockHash,
         bytes32 randomNumber
     );
+    event RevealedWithCallback(
+        EntropyStructs.Request request,
+        bytes32 userRandomNumber,
+        bytes32 providerRevelation,
+        bytes32 randomNumber
+    );
 
     event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee);
 

+ 3 - 1
target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol

@@ -54,6 +54,8 @@ contract EntropyStructs {
         address requester;
         // If true, incorporate the blockhash of blockNumber into the generated random value.
         bool useBlockhash;
-        // There are 3 remaining bytes of free space in this slot.
+        // If true, the requester will be called back with the generated random value.
+        bool isRequestWithCallback;
+        // There are 2 remaining bytes of free space in this slot.
     }
 }

+ 31 - 2
target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol

@@ -38,7 +38,20 @@ interface IEntropy is EntropyEvents {
         bool useBlockHash
     ) external payable returns (uint64 assignedSequenceNumber);
 
-    // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
+    // Request a random number. The method expects the provider address and a secret random number
+    // in the arguments. It returns a sequence number.
+    //
+    // The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
+    // The `entropyCallback` method on that interface will receive a callback with the generated random number.
+    //
+    // This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
+    // Note that excess value is *not* refunded to the caller.
+    function requestWithCallback(
+        address provider,
+        bytes32 userRandomNumber
+    ) external payable returns (uint64 assignedSequenceNumber);
+
+    // Fulfill a request for a random number. This method validates the provided userRevelation and provider's proof
     // against the corresponding commitments in the in-flight request. If both values are validated, this function returns
     // the corresponding random number.
     //
@@ -48,10 +61,26 @@ interface IEntropy is EntropyEvents {
     function reveal(
         address provider,
         uint64 sequenceNumber,
-        bytes32 userRandomness,
+        bytes32 userRevelation,
         bytes32 providerRevelation
     ) external returns (bytes32 randomNumber);
 
+    // Fulfill a request for a random number and call back the requester. This method validates the provided userRandomness
+    // and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated,
+    // this function calls the requester's entropyCallback method with the sequence number and the random number as arguments.
+    //
+    // Note that this function can only be called once per in-flight request. Calling this function deletes the stored
+    // request information (so that the contract doesn't use a linear amount of storage in the number of requests).
+    // If you need to use the returned random number more than once, you are responsible for storing it.
+    //
+    // Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
+    function revealWithCallback(
+        address provider,
+        uint64 sequenceNumber,
+        bytes32 userRandomNumber,
+        bytes32 providerRevelation
+    ) external;
+
     function getProviderInfo(
         address provider
     ) external view returns (EntropyStructs.ProviderInfo memory info);

+ 28 - 0
target_chains/ethereum/entropy_sdk/solidity/IEntropyConsumer.sol

@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: Apache 2
+pragma solidity ^0.8.0;
+
+abstract contract IEntropyConsumer {
+    // This method is called by Entropy to provide the random number to the consumer.
+    // It asserts that the msg.sender is the Entropy contract. It is not meant to be
+    // override by the consumer.
+    function _entropyCallback(uint64 sequence, bytes32 randomNumber) external {
+        address entropy = getEntropy();
+        require(entropy != address(0), "Entropy address not set");
+        require(msg.sender == entropy, "Only Entropy can call this function");
+
+        entropyCallback(sequence, randomNumber);
+    }
+
+    // getEntropy returns Entropy contract address. The method is being used to check that the
+    // callback is indeed from Entropy contract. The consumer is expected to implement this method.
+    // Entropy address can be found here - https://docs.pyth.network/entropy/contract-addresses
+    function getEntropy() internal view virtual returns (address);
+
+    // This method is expected to be implemented by the consumer to handle the random number.
+    // It will be called by _entropyCallback after _entropyCallback ensures that the call is
+    // indeed from Entropy contract.
+    function entropyCallback(
+        uint64 sequence,
+        bytes32 randomNumber
+    ) internal virtual;
+}

+ 5 - 0
target_chains/ethereum/entropy_sdk/solidity/abis/EntropyErrors.json

@@ -19,6 +19,11 @@
     "name": "InsufficientFee",
     "type": "error"
   },
+  {
+    "inputs": [],
+    "name": "InvalidRevealCall",
+    "type": "error"
+  },
   {
     "inputs": [],
     "name": "InvalidUpgradeMagic",

+ 162 - 0
target_chains/ethereum/entropy_sdk/solidity/abis/EntropyEvents.json

@@ -153,6 +153,11 @@
             "internalType": "bool",
             "name": "useBlockhash",
             "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
           }
         ],
         "indexed": false,
@@ -167,6 +172,30 @@
   {
     "anonymous": false,
     "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "provider",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "requestor",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint64",
+        "name": "sequenceNumber",
+        "type": "uint64"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "userRandomNumber",
+        "type": "bytes32"
+      },
       {
         "components": [
           {
@@ -203,6 +232,66 @@
             "internalType": "bool",
             "name": "useBlockhash",
             "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
+          }
+        ],
+        "indexed": false,
+        "internalType": "struct EntropyStructs.Request",
+        "name": "request",
+        "type": "tuple"
+      }
+    ],
+    "name": "RequestedWithCallback",
+    "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "components": [
+          {
+            "internalType": "address",
+            "name": "provider",
+            "type": "address"
+          },
+          {
+            "internalType": "uint64",
+            "name": "sequenceNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "uint32",
+            "name": "numHashes",
+            "type": "uint32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "commitment",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "uint64",
+            "name": "blockNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "address",
+            "name": "requester",
+            "type": "address"
+          },
+          {
+            "internalType": "bool",
+            "name": "useBlockhash",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
           }
         ],
         "indexed": false,
@@ -237,5 +326,78 @@
     ],
     "name": "Revealed",
     "type": "event"
+  },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "components": [
+          {
+            "internalType": "address",
+            "name": "provider",
+            "type": "address"
+          },
+          {
+            "internalType": "uint64",
+            "name": "sequenceNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "uint32",
+            "name": "numHashes",
+            "type": "uint32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "commitment",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "uint64",
+            "name": "blockNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "address",
+            "name": "requester",
+            "type": "address"
+          },
+          {
+            "internalType": "bool",
+            "name": "useBlockhash",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
+          }
+        ],
+        "indexed": false,
+        "internalType": "struct EntropyStructs.Request",
+        "name": "request",
+        "type": "tuple"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "userRandomNumber",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "providerRevelation",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "randomNumber",
+        "type": "bytes32"
+      }
+    ],
+    "name": "RevealedWithCallback",
+    "type": "event"
   }
 ]

+ 220 - 1
target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json

@@ -153,6 +153,11 @@
             "internalType": "bool",
             "name": "useBlockhash",
             "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
           }
         ],
         "indexed": false,
@@ -164,6 +169,85 @@
     "name": "Requested",
     "type": "event"
   },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "provider",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "address",
+        "name": "requestor",
+        "type": "address"
+      },
+      {
+        "indexed": true,
+        "internalType": "uint64",
+        "name": "sequenceNumber",
+        "type": "uint64"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "userRandomNumber",
+        "type": "bytes32"
+      },
+      {
+        "components": [
+          {
+            "internalType": "address",
+            "name": "provider",
+            "type": "address"
+          },
+          {
+            "internalType": "uint64",
+            "name": "sequenceNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "uint32",
+            "name": "numHashes",
+            "type": "uint32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "commitment",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "uint64",
+            "name": "blockNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "address",
+            "name": "requester",
+            "type": "address"
+          },
+          {
+            "internalType": "bool",
+            "name": "useBlockhash",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
+          }
+        ],
+        "indexed": false,
+        "internalType": "struct EntropyStructs.Request",
+        "name": "request",
+        "type": "tuple"
+      }
+    ],
+    "name": "RequestedWithCallback",
+    "type": "event"
+  },
   {
     "anonymous": false,
     "inputs": [
@@ -203,6 +287,11 @@
             "internalType": "bool",
             "name": "useBlockhash",
             "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
           }
         ],
         "indexed": false,
@@ -238,6 +327,79 @@
     "name": "Revealed",
     "type": "event"
   },
+  {
+    "anonymous": false,
+    "inputs": [
+      {
+        "components": [
+          {
+            "internalType": "address",
+            "name": "provider",
+            "type": "address"
+          },
+          {
+            "internalType": "uint64",
+            "name": "sequenceNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "uint32",
+            "name": "numHashes",
+            "type": "uint32"
+          },
+          {
+            "internalType": "bytes32",
+            "name": "commitment",
+            "type": "bytes32"
+          },
+          {
+            "internalType": "uint64",
+            "name": "blockNumber",
+            "type": "uint64"
+          },
+          {
+            "internalType": "address",
+            "name": "requester",
+            "type": "address"
+          },
+          {
+            "internalType": "bool",
+            "name": "useBlockhash",
+            "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
+          }
+        ],
+        "indexed": false,
+        "internalType": "struct EntropyStructs.Request",
+        "name": "request",
+        "type": "tuple"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "userRandomNumber",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "providerRevelation",
+        "type": "bytes32"
+      },
+      {
+        "indexed": false,
+        "internalType": "bytes32",
+        "name": "randomNumber",
+        "type": "bytes32"
+      }
+    ],
+    "name": "RevealedWithCallback",
+    "type": "event"
+  },
   {
     "inputs": [
       {
@@ -453,6 +615,11 @@
             "internalType": "bool",
             "name": "useBlockhash",
             "type": "bool"
+          },
+          {
+            "internalType": "bool",
+            "name": "isRequestWithCallback",
+            "type": "bool"
           }
         ],
         "internalType": "struct EntropyStructs.Request",
@@ -525,6 +692,30 @@
     "stateMutability": "payable",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "provider",
+        "type": "address"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "userRandomNumber",
+        "type": "bytes32"
+      }
+    ],
+    "name": "requestWithCallback",
+    "outputs": [
+      {
+        "internalType": "uint64",
+        "name": "assignedSequenceNumber",
+        "type": "uint64"
+      }
+    ],
+    "stateMutability": "payable",
+    "type": "function"
+  },
   {
     "inputs": [
       {
@@ -539,7 +730,7 @@
       },
       {
         "internalType": "bytes32",
-        "name": "userRandomness",
+        "name": "userRevelation",
         "type": "bytes32"
       },
       {
@@ -559,6 +750,34 @@
     "stateMutability": "nonpayable",
     "type": "function"
   },
+  {
+    "inputs": [
+      {
+        "internalType": "address",
+        "name": "provider",
+        "type": "address"
+      },
+      {
+        "internalType": "uint64",
+        "name": "sequenceNumber",
+        "type": "uint64"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "userRandomNumber",
+        "type": "bytes32"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "providerRevelation",
+        "type": "bytes32"
+      }
+    ],
+    "name": "revealWithCallback",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  },
   {
     "inputs": [
       {

+ 20 - 0
target_chains/ethereum/entropy_sdk/solidity/abis/IEntropyConsumer.json

@@ -0,0 +1,20 @@
+[
+  {
+    "inputs": [
+      {
+        "internalType": "uint64",
+        "name": "sequence",
+        "type": "uint64"
+      },
+      {
+        "internalType": "bytes32",
+        "name": "randomNumber",
+        "type": "bytes32"
+      }
+    ],
+    "name": "_entropyCallback",
+    "outputs": [],
+    "stateMutability": "nonpayable",
+    "type": "function"
+  }
+]

+ 2 - 2
target_chains/ethereum/entropy_sdk/solidity/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@pythnetwork/entropy-sdk-solidity",
-  "version": "1.1.3",
+  "version": "1.2.0",
   "description": "Generate secure random numbers with Pyth Entropy",
   "repository": {
     "type": "git",
@@ -12,7 +12,7 @@
   },
   "scripts": {
     "format": "npx prettier --write .",
-    "generate-abi": "npx generate-abis IEntropy EntropyErrors EntropyEvents EntropyStructs",
+    "generate-abi": "npx generate-abis IEntropy IEntropyConsumer EntropyErrors EntropyEvents EntropyStructs",
     "check-abi": "git diff --exit-code abis"
   },
   "keywords": [