Quellcode durchsuchen

feat(Entropy): Include gas limits and gas usage in events (#2627)

* initial commit: gas limit in request

* new log testing

* fix the test

* minor tweaks

* minor tweak

* better gas computation
Jayant Krishnamurthy vor 6 Monaten
Ursprung
Commit
6153eaca38

+ 9 - 6
target_chains/ethereum/contracts/contracts/entropy/Entropy.sol

@@ -369,6 +369,7 @@ abstract contract Entropy is IEntropy, EntropyState {
             req.requester,
             req.sequenceNumber,
             userRandomNumber,
+            uint32(req.gasLimit10k) * TEN_THOUSAND,
             bytes("")
         );
         return req.sequenceNumber;
@@ -543,16 +544,13 @@ abstract contract Entropy is IEntropy, EntropyState {
             revert EntropyErrors.InvalidRevealCall();
         }
 
-        bytes32 blockHash;
         bytes32 randomNumber;
-        (randomNumber, blockHash) = revealHelper(
+        (randomNumber, ) = revealHelper(
             req,
             userRandomNumber,
             providerRevelation
         );
 
-        address callAddress = req.requester;
-
         // If the request has an explicit gas limit, then run the new callback failure state flow.
         //
         // Requests that haven't been invoked yet will be invoked safely (catching reverts), and
@@ -567,7 +565,7 @@ abstract contract Entropy is IEntropy, EntropyState {
             bool success;
             bytes memory ret;
             uint256 startingGas = gasleft();
-            (success, ret) = callAddress.excessivelySafeCall(
+            (success, ret) = req.requester.excessivelySafeCall(
                 // Warning: the provided gas limit below is only an *upper bound* on the gas provided to the call.
                 // At most 63/64ths of the current context's gas will be provided to a call, which may be less
                 // than the indicated gas limit. (See CALL opcode docs here https://www.evm.codes/?fork=cancun#f1)
@@ -582,6 +580,7 @@ abstract contract Entropy is IEntropy, EntropyState {
                     randomNumber
                 )
             );
+            uint32 gasUsed = SafeCast.toUint32(startingGas - gasleft());
             // Reset status to not started here in case the transaction reverts.
             req.callbackStatus = EntropyStatusConstants.CALLBACK_NOT_STARTED;
 
@@ -599,6 +598,7 @@ abstract contract Entropy is IEntropy, EntropyState {
                     randomNumber,
                     false,
                     ret,
+                    SafeCast.toUint32(gasUsed),
                     bytes("")
                 );
                 clearRequest(provider, sequenceNumber);
@@ -628,6 +628,7 @@ abstract contract Entropy is IEntropy, EntropyState {
                     randomNumber,
                     true,
                     ret,
+                    SafeCast.toUint32(gasUsed),
                     bytes("")
                 );
                 req.callbackStatus = EntropyStatusConstants.CALLBACK_FAILED;
@@ -655,13 +656,15 @@ abstract contract Entropy is IEntropy, EntropyState {
                 randomNumber,
                 false,
                 bytes(""),
+                0, // gas usage not tracked in the old no-gas-limit flow.
                 bytes("")
             );
 
             clearRequest(provider, sequenceNumber);
 
-            // Check if the callAddress is a contract account.
+            // Check if the requester is a contract account.
             uint len;
+            address callAddress = req.requester;
             assembly {
                 len := extcodesize(callAddress)
             }

+ 150 - 59
target_chains/ethereum/contracts/forge-test/Entropy.t.sol

@@ -825,6 +825,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             user1,
             providerInfo.sequenceNumber,
             userRandomNumber,
+            0,
             bytes("")
         );
         vm.roll(1234);
@@ -877,7 +878,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
                 0
             )
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             req.requester,
@@ -889,6 +890,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             false,
             bytes(""),
+            0,
             bytes("")
         );
         vm.prank(user1);
@@ -944,7 +946,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
                 0
             )
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             req.requester,
@@ -956,6 +958,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             false,
             bytes(""),
+            0,
             bytes("")
         );
         random.revealWithCallback(
@@ -1018,6 +1021,15 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
         uint fee = random.getFee(provider1);
         EntropyConsumer consumer = new EntropyConsumer(address(random), false);
         vm.deal(user1, fee);
+        vm.expectEmit(false, false, false, true, address(random));
+        emit EntropyEventsV2.Requested(
+            provider1,
+            user1,
+            0,
+            userRandomNumber,
+            100000,
+            bytes("")
+        );
         vm.prank(user1);
         uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}(
             userRandomNumber
@@ -1041,7 +1053,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
                 0
             )
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             req.requester,
@@ -1053,6 +1065,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             false,
             bytes(""),
+            0,
             bytes("")
         );
         random.revealWithCallback(
@@ -1113,7 +1126,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             revertReason
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             address(consumer),
@@ -1125,6 +1138,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             true,
             revertReason,
+            0,
             bytes("")
         );
         random.revealWithCallback(
@@ -1178,7 +1192,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
                 0
             )
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             reqAfterFailure.requester,
@@ -1190,6 +1204,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             false,
             bytes(""),
+            0,
             bytes("")
         );
         random.revealWithCallback(
@@ -1258,7 +1273,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             // out-of-gas reverts have an empty bytes array as the return value.
             ""
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             address(consumer),
@@ -1270,7 +1285,8 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             true,
             "",
-            ""
+            0,
+            bytes("")
         );
         random.revealWithCallback(
             provider1,
@@ -1322,7 +1338,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
                 0
             )
         );
-        vm.expectEmit(true, true, true, true, address(random));
+        vm.expectEmit(true, true, true, false, address(random));
         emit EntropyEventsV2.Revealed(
             provider1,
             address(consumer),
@@ -1334,7 +1350,8 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             ),
             false,
             "",
-            ""
+            0,
+            bytes("")
         );
         random.revealWithCallback(
             provider1,
@@ -1718,9 +1735,19 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
             gasLimit
         );
 
-        uint128 startingAccruedProviderFee = random
-            .getProviderInfoV2(provider1)
-            .accruedFeesInWei;
+        EntropyStructsV2.ProviderInfo memory providerInfo = random
+            .getProviderInfoV2(provider1);
+
+        uint128 startingAccruedProviderFee = providerInfo.accruedFeesInWei;
+        vm.expectEmit(true, true, true, true, address(random));
+        emit EntropyEventsV2.Requested(
+            provider1,
+            user1,
+            providerInfo.sequenceNumber,
+            userRandomNumber,
+            uint32(expectedGasLimit10k) * 10000,
+            bytes("")
+        );
         vm.prank(user1);
         uint64 sequenceNumber = random.requestWithCallbackAndGasLimit{
             value: fee
@@ -1787,41 +1814,69 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
         );
 
         if (!expectSuccess) {
-            vm.expectEmit(true, true, true, true, address(random));
-            emit CallbackFailed(
+            vm.recordLogs();
+            random.revealWithCallback(
                 provider1,
-                address(consumer),
                 sequenceNumber,
                 userRandomNumber,
-                provider1Proofs[sequenceNumber],
-                random.combineRandomValues(
-                    userRandomNumber,
-                    provider1Proofs[sequenceNumber],
-                    0
-                ),
-                // out-of-gas reverts have an empty bytes array as the return value.
-                ""
+                provider1Proofs[sequenceNumber]
             );
-            vm.expectEmit(true, true, true, true, address(random));
-            emit EntropyEventsV2.Revealed(
-                provider1,
-                address(consumer),
-                sequenceNumber,
+            Vm.Log[] memory entries = vm.getRecordedLogs();
+
+            assertEq(entries.length, 2);
+            // first entry is CallbackFailed which we aren't going to check.
+            // Unfortunately event.selector was added in Solidity 0.8.15 and we're on 0.8.4 so we have to copy this spec here.
+            assertEq(
+                entries[1].topics[0],
+                keccak256(
+                    "Revealed(address,address,uint64,bytes32,bool,bytes,uint32,bytes)"
+                )
+            );
+            // Verify the topics match the expected values
+            assertEq(
+                entries[1].topics[1],
+                bytes32(uint256(uint160(provider1)))
+            );
+            assertEq(
+                entries[1].topics[2],
+                bytes32(uint256(uint160(address(consumer))))
+            );
+            assertEq(entries[1].topics[3], bytes32(uint256(sequenceNumber)));
+
+            // Verify the data field contains the expected values (per event ABI)
+            (
+                bytes32 randomNumber,
+                bool callbackFailed,
+                bytes memory callbackErrorCode,
+                uint32 callbackGasUsed,
+                bytes memory extraArgs
+            ) = abi.decode(
+                    entries[1].data,
+                    (bytes32, bool, bytes, uint32, bytes)
+                );
+
+            assertEq(
+                randomNumber,
                 random.combineRandomValues(
                     userRandomNumber,
                     provider1Proofs[sequenceNumber],
                     0
-                ),
-                true,
-                bytes(""),
-                bytes("")
+                )
             );
-            random.revealWithCallback(
-                provider1,
-                sequenceNumber,
-                userRandomNumber,
-                provider1Proofs[sequenceNumber]
+            assertEq(callbackFailed, true);
+            assertEq(callbackErrorCode, bytes(""));
+
+            // callback gas usage is approximate and only triggered when the provider has set a gas limit.
+            // Note: this condition is somewhat janky, but we hit the stack limit so can't put in any more local variables :(
+            assertTrue(
+                random.getProviderInfoV2(provider1).defaultGasLimit == 0 ||
+                    ((callbackGasUsage * 90) / 100 < callbackGasUsed)
             );
+            assertTrue(
+                random.getProviderInfoV2(provider1).defaultGasLimit == 0 ||
+                    (callbackGasUsed < (callbackGasUsage * 110) / 100)
+            );
+            assertEq(extraArgs, bytes(""));
 
             // Verify request is still active after failure
             EntropyStructsV2.Request memory reqAfterFailure = random
@@ -1832,37 +1887,73 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents, EntropyEventsV2 {
                 EntropyStatusConstants.CALLBACK_FAILED
             );
         } else {
-            vm.expectEmit(true, true, true, true, address(random));
-            emit RevealedWithCallback(
-                EntropyStructConverter.toV1Request(req),
+            vm.recordLogs();
+            random.revealWithCallback(
+                provider1,
+                sequenceNumber,
                 userRandomNumber,
-                provider1Proofs[sequenceNumber],
-                random.combineRandomValues(
-                    userRandomNumber,
-                    provider1Proofs[sequenceNumber],
-                    0
+                provider1Proofs[sequenceNumber]
+            );
+
+            Vm.Log[] memory entries = vm.getRecordedLogs();
+
+            assertEq(entries.length, 2);
+            // first entry is CallbackFailed which we aren't going to check.
+            // Unfortunately event.selector was added in Solidity 0.8.15 and we're on 0.8.4 so we have to copy this spec here.
+            assertEq(
+                entries[1].topics[0],
+                keccak256(
+                    "Revealed(address,address,uint64,bytes32,bool,bytes,uint32,bytes)"
                 )
             );
-            vm.expectEmit(true, true, true, true, address(random));
-            emit EntropyEventsV2.Revealed(
-                provider1,
-                req.requester,
-                req.sequenceNumber,
+
+            // Verify the topics match the expected values
+            assertEq(
+                entries[1].topics[1],
+                bytes32(uint256(uint160(provider1)))
+            );
+            assertEq(
+                entries[1].topics[2],
+                bytes32(uint256(uint160(req.requester)))
+            );
+            assertEq(
+                entries[1].topics[3],
+                bytes32(uint256(req.sequenceNumber))
+            );
+
+            // Verify the data field contains the expected values (per event ABI)
+            (
+                bytes32 randomNumber,
+                bool callbackFailed,
+                bytes memory callbackErrorCode,
+                uint32 callbackGasUsed,
+                bytes memory extraArgs
+            ) = abi.decode(
+                    entries[1].data,
+                    (bytes32, bool, bytes, uint32, bytes)
+                );
+
+            assertEq(
+                randomNumber,
                 random.combineRandomValues(
                     userRandomNumber,
                     provider1Proofs[sequenceNumber],
                     0
-                ),
-                false,
-                bytes(""),
-                bytes("")
+                )
             );
-            random.revealWithCallback(
-                provider1,
-                sequenceNumber,
-                userRandomNumber,
-                provider1Proofs[sequenceNumber]
+            assertEq(callbackFailed, false);
+            assertEq(callbackErrorCode, bytes(""));
+            // callback gas usage is approximate and only triggered when the provider has set a gas limit
+            // Note: this condition is somewhat janky, but we hit the stack limit so can't put in any more local variables :(
+            assertTrue(
+                random.getProviderInfoV2(provider1).defaultGasLimit == 0 ||
+                    ((callbackGasUsage * 90) / 100 < callbackGasUsed)
+            );
+            assertTrue(
+                random.getProviderInfoV2(provider1).defaultGasLimit == 0 ||
+                    (callbackGasUsed < (callbackGasUsage * 110) / 100)
             );
+            assertEq(extraArgs, bytes(""));
 
             // Verify request is cleared after successful callback
             EntropyStructsV2.Request memory reqAfterSuccess = random

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

@@ -24,6 +24,7 @@ interface EntropyEventsV2 {
      * @param caller The address of the user requesting the random number
      * @param sequenceNumber A unique identifier for this request
      * @param userRandomNumber A random number provided by the user for additional entropy
+     * @param gasLimit The gas limit for the callback.
      * @param extraArgs A field for extra data for forward compatibility.
      */
     event Requested(
@@ -31,6 +32,7 @@ interface EntropyEventsV2 {
         address indexed caller,
         uint64 indexed sequenceNumber,
         bytes32 userRandomNumber,
+        uint32 gasLimit,
         bytes extraArgs
     );
 
@@ -44,6 +46,7 @@ interface EntropyEventsV2 {
      * @param callbackReturnValue Return value from the callback. If the callback failed, this field contains
      * the error code and any additional returned data. Note that "" often indicates an out-of-gas error.
      * If the callback returns more than 256 bytes, only the first 256 bytes of the callback return value are included.
+     * @param callbackGasUsed How much gas the callback used.
      * @param extraArgs A field for extra data for forward compatibility.
      */
     event Revealed(
@@ -53,6 +56,7 @@ interface EntropyEventsV2 {
         bytes32 randomNumber,
         bool callbackFailed,
         bytes callbackReturnValue,
+        uint32 callbackGasUsed,
         bytes extraArgs
     );
 

+ 12 - 0
target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json

@@ -504,6 +504,12 @@
         "name": "userRandomNumber",
         "type": "bytes32"
       },
+      {
+        "indexed": false,
+        "internalType": "uint32",
+        "name": "gasLimit",
+        "type": "uint32"
+      },
       {
         "indexed": false,
         "internalType": "bytes",
@@ -711,6 +717,12 @@
         "name": "callbackReturnValue",
         "type": "bytes"
       },
+      {
+        "indexed": false,
+        "internalType": "uint32",
+        "name": "callbackGasUsed",
+        "type": "uint32"
+      },
       {
         "indexed": false,
         "internalType": "bytes",