Ver código fonte

Add new request method to check original commitment

Amin Moghaddam 1 ano atrás
pai
commit
94100c95c8

+ 56 - 2
target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

@@ -269,10 +269,10 @@ abstract contract Entropy is IEntropy, EntropyState {
         emit Requested(req);
     }
 
-    // Request a random number. The method expects the provider address and a secret random number
+    // Request a random number. The method expects the provider address and the user 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 address calling this function should be an EOA or a contract that implements 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.
@@ -302,6 +302,48 @@ abstract contract Entropy is IEntropy, EntropyState {
         return req.sequenceNumber;
     }
 
+    // Request a random number. The method expects the provider address and the user random number
+    // in the arguments. It returns a sequence number.
+    //
+    // The address calling this function should be an EOA or a contract that implements 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. This method will also revert if the provider's original
+    // commitment does not match the one on-chain which is necessary to prevent a scenario where the provider rotates
+    // their commitment while the transaction is pending.
+    function requestWithCallback(
+        address provider,
+        bytes32 userRandomNumber,
+        bytes32 providerOriginalCommitment
+    ) public payable override returns (uint64) {
+        EntropyStructs.Request storage req = requestHelper(
+            provider,
+            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
+        );
+
+        EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
+            provider
+        ];
+        if (providerInfo.originalCommitment != providerOriginalCommitment)
+            revert EntropyErrors.InvalidOriginalProviderCommitment();
+
+        emit RequestedWithCallback(
+            provider,
+            req.requester,
+            req.sequenceNumber,
+            userRandomNumber,
+            req
+        );
+
+        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.
@@ -480,6 +522,18 @@ abstract contract Entropy is IEntropy, EntropyState {
         return _state.providers[provider].feeInWei + _state.pythFeeInWei;
     }
 
+    function getFeeAndOriginalCommitment(
+        address provider
+    ) public view override returns (uint128 feeAmount, bytes32 commitment) {
+        EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
+            provider
+        ];
+        return (
+            providerInfo.feeInWei + _state.pythFeeInWei,
+            providerInfo.originalCommitment
+        );
+    }
+
     function getPythFee() public view returns (uint128 feeAmount) {
         return _state.pythFeeInWei;
     }

+ 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.3.1";
+        return "0.4.0";
     }
 }

+ 73 - 0
target_chains/ethereum/contracts/forge-test/Entropy.t.sol

@@ -891,6 +891,79 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
         assertEq(reqAfterReveal.sequenceNumber, 0);
     }
 
+    function testRequestWithCallbackAndRevealWithCallbackByEoaWithOriginalCommitment()
+        public
+    {
+        bytes32 userRandomNumber = bytes32(uint(42));
+        (uint fee, bytes32 originalCommitment) = random
+            .getFeeAndOriginalCommitment(provider1);
+        vm.deal(user1, fee);
+        vm.prank(user1);
+
+        bytes32 invalidOriginalCommitment = bytes32(0);
+        vm.expectRevert(
+            EntropyErrors.InvalidOriginalProviderCommitment.selector
+        );
+        random.requestWithCallback{value: fee}(
+            provider1,
+            userRandomNumber,
+            invalidOriginalCommitment
+        );
+
+        vm.prank(user1);
+        uint64 assignedSequenceNumber = random.requestWithCallback{value: fee}(
+            provider1,
+            userRandomNumber,
+            originalCommitment
+        );
+        random.revealWithCallback(
+            provider1,
+            assignedSequenceNumber,
+            userRandomNumber,
+            provider1Proofs[assignedSequenceNumber]
+        );
+
+        EntropyStructs.Request memory reqAfterReveal = random.getRequest(
+            provider1,
+            assignedSequenceNumber
+        );
+        assertEq(reqAfterReveal.sequenceNumber, 0);
+    }
+
+    function testInvalidOriginalCommitmentIfProviderRotates() public {
+        bytes32 userRandomNumber = bytes32(uint(42));
+        (uint fee, bytes32 originalCommitment) = random
+            .getFeeAndOriginalCommitment(provider1);
+
+        uint64 newHashChainOffset = random
+            .getProviderInfo(provider1)
+            .sequenceNumber + 1;
+        bytes32[] memory newHashChain = generateHashChain(
+            provider1,
+            newHashChainOffset,
+            10
+        );
+        vm.prank(provider1);
+        random.register(
+            provider1FeeInWei,
+            newHashChain[0],
+            hex"0100",
+            10,
+            provider1Uri
+        );
+
+        vm.deal(user1, fee);
+        vm.prank(user1);
+        vm.expectRevert(
+            EntropyErrors.InvalidOriginalProviderCommitment.selector
+        );
+        random.requestWithCallback{value: fee}(
+            provider1,
+            userRandomNumber,
+            originalCommitment
+        );
+    }
+
     function testRequestAndRevealWithCallback() public {
         uint64 sequenceNumber = request(user2, provider1, 42, false);
         assertEq(random.getRequest(provider1, sequenceNumber).requester, user2);

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

@@ -9,6 +9,10 @@ library EntropyErrors {
     // The provider being registered has already registered
     // Signature: 0xda041bdf
     error ProviderAlreadyRegistered();
+    // The original provider commitment is not the same as the one on-chain.
+    // Perhaps it was rotated while the transaction was pending.
+    // Signature: 0x03e97a06
+    error InvalidOriginalProviderCommitment();
     // The requested provider does not exist.
     // Signature: 0xdf51c431
     error NoSuchProvider();

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

@@ -43,10 +43,10 @@ interface IEntropy is EntropyEvents {
         bool useBlockHash
     ) external payable returns (uint64 assignedSequenceNumber);
 
-    // Request a random number. The method expects the provider address and a secret random number
+    // Request a random number. The method expects the provider address and the user 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 address calling this function should be an EOA or a contract that implements 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.
@@ -56,6 +56,22 @@ interface IEntropy is EntropyEvents {
         bytes32 userRandomNumber
     ) external payable returns (uint64 assignedSequenceNumber);
 
+    // Request a random number. The method expects the provider address and the user random number
+    // in the arguments. It returns a sequence number.
+    //
+    // The address calling this function should be an EOA or a contract that implements 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. This method will also revert if the provider's original
+    // commitment does not match the one on-chain which is necessary to prevent a scenario where the provider rotates
+    // their commitment while the transaction is pending.
+    function requestWithCallback(
+        address provider,
+        bytes32 userRandomNumber,
+        bytes32 providerOriginalCommitment
+    ) external payable returns (uint64 assignedSequenceNumber);
+
     // 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.
@@ -100,6 +116,11 @@ interface IEntropy is EntropyEvents {
 
     function getFee(address provider) external view returns (uint128 feeAmount);
 
+    // Utility function to get all the necessary information for submitting a request in one call.
+    function getFeeAndOriginalCommitment(
+        address provider
+    ) external view returns (uint128 feeAmount, bytes32 commitment);
+
     function getAccruedPythFees()
         external
         view