@@ -1003,7 +1003,9 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
10031003 assertEq (reqAfterReveal.sequenceNumber, 0 );
10041004 }
10051005
1006- function testRequestWithCallbackAndRevealWithCallbackWithGasLimitAndFailure () public {
1006+ function testRequestWithCallbackAndRevealWithCallbackWithGasLimitAndFailure ()
1007+ public
1008+ {
10071009 uint64 defaultGasLimit = 100000 ;
10081010 vm.prank (provider1);
10091011 random.setDefaultGasLimit (defaultGasLimit);
@@ -1025,7 +1027,10 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
10251027 assertEq (req.blockNumberOrGasLimit, defaultGasLimit);
10261028
10271029 // On the first attempt, the transaction should succeed and emit CallbackFailed event.
1028- bytes memory revertReason = abi.encodeWithSelector (0x08c379a0 , "Callback failed " );
1030+ bytes memory revertReason = abi.encodeWithSelector (
1031+ 0x08c379a0 ,
1032+ "Callback failed "
1033+ );
10291034 vm.expectEmit (false , false , false , true , address (random));
10301035 emit CallbackFailed (
10311036 provider1,
@@ -1058,15 +1063,11 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
10581063 );
10591064
10601065 // Again, request stays active after failure
1061- reqAfterFailure = random.getRequest (
1062- provider1,
1063- assignedSequenceNumber
1064- );
1066+ reqAfterFailure = random.getRequest (provider1, assignedSequenceNumber);
10651067 assertEq (reqAfterFailure.sequenceNumber, assignedSequenceNumber);
10661068 assertTrue (reqAfterFailure.callbackFailed);
10671069
1068- // If the callback starts succeeding, we can invoke it and
1069- // it emits RevealedWithCallback event.
1070+ // If the callback starts succeeding, we can invoke it and it emits the usual RevealedWithCallback event.
10701071 consumer.setReverts (false );
10711072 vm.expectEmit (false , false , false , true , address (random));
10721073 emit RevealedWithCallback (
@@ -1097,10 +1098,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
10971098 function testRequestWithCallbackAndRevealWithCallbackFailing () public {
10981099 bytes32 userRandomNumber = bytes32 (uint (42 ));
10991100 uint fee = random.getFee (provider1);
1100- EntropyConsumer consumer = new EntropyConsumer (
1101- address (random),
1102- true
1103- );
1101+ EntropyConsumer consumer = new EntropyConsumer (address (random), true );
11041102 vm.deal (address (consumer), fee);
11051103 vm.startPrank (address (consumer));
11061104 uint64 assignedSequenceNumber = random.requestWithCallback {value: fee}(
@@ -1431,6 +1429,108 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
14311429 // Fee should be the same as base fee since we're using less gas than default
14321430 assertEq (fee, random.getFee (provider1));
14331431 }
1432+
1433+ function testRequestWithCallbackAndRevealWithCallbackWithGasLimitAndFailureWithGasUsage ()
1434+ public
1435+ {
1436+ uint64 defaultGasLimit = 100000 ;
1437+ vm.prank (provider1);
1438+ random.setDefaultGasLimit (defaultGasLimit);
1439+
1440+ bytes32 userRandomNumber = bytes32 (uint (42 ));
1441+ uint fee = random.getFee (provider1);
1442+ EntropyConsumer consumer = new EntropyConsumer (address (random), false );
1443+ // Consumer callback uses ~10% more gas than the provider's default
1444+ consumer.setTargetGasUsage ((defaultGasLimit * 110 ) / 100 );
1445+
1446+ vm.deal (user1, fee);
1447+ vm.prank (user1);
1448+ uint64 assignedSequenceNumber = consumer.requestEntropy {value: fee}(
1449+ userRandomNumber
1450+ );
1451+ EntropyStructs.Request memory req = random.getRequest (
1452+ provider1,
1453+ assignedSequenceNumber
1454+ );
1455+
1456+ // Verify the gas limit was set correctly
1457+ assertEq (req.blockNumberOrGasLimit, defaultGasLimit);
1458+
1459+ // The transaction reverts if the provider does not provide enough gas to forward
1460+ // the gasLimit to the callback transaction.
1461+ vm.expectRevert ();
1462+ random.revealWithCallback {gas: defaultGasLimit - 10000 }(
1463+ provider1,
1464+ assignedSequenceNumber,
1465+ userRandomNumber,
1466+ provider1Proofs[assignedSequenceNumber]
1467+ );
1468+
1469+ // If called with enough gas, the transaction should succeed, but the callback should fail because
1470+ // it uses too much gas.
1471+ vm.expectEmit (false , false , false , true , address (random));
1472+ emit CallbackFailed (
1473+ provider1,
1474+ address (consumer),
1475+ assignedSequenceNumber,
1476+ // out-of-gas reverts have an empty bytes array as the return value.
1477+ ""
1478+ );
1479+ random.revealWithCallback (
1480+ provider1,
1481+ assignedSequenceNumber,
1482+ userRandomNumber,
1483+ provider1Proofs[assignedSequenceNumber]
1484+ );
1485+
1486+ // Verify request is still active after failure
1487+ EntropyStructs.Request memory reqAfterFailure = random.getRequest (
1488+ provider1,
1489+ assignedSequenceNumber
1490+ );
1491+ assertEq (reqAfterFailure.sequenceNumber, assignedSequenceNumber);
1492+ assertTrue (reqAfterFailure.callbackFailed);
1493+
1494+ // A subsequent attempt passing insufficient gas should also revert
1495+ vm.expectRevert ();
1496+ random.revealWithCallback {gas: defaultGasLimit - 10000 }(
1497+ provider1,
1498+ assignedSequenceNumber,
1499+ userRandomNumber,
1500+ provider1Proofs[assignedSequenceNumber]
1501+ );
1502+
1503+ // Again, request stays active after failure
1504+ reqAfterFailure = random.getRequest (provider1, assignedSequenceNumber);
1505+ assertEq (reqAfterFailure.sequenceNumber, assignedSequenceNumber);
1506+ assertTrue (reqAfterFailure.callbackFailed);
1507+
1508+ // Calling without a gas limit should succeed
1509+ vm.expectEmit (false , false , false , true , address (random));
1510+ emit RevealedWithCallback (
1511+ reqAfterFailure,
1512+ userRandomNumber,
1513+ provider1Proofs[assignedSequenceNumber],
1514+ random.combineRandomValues (
1515+ userRandomNumber,
1516+ provider1Proofs[assignedSequenceNumber],
1517+ 0
1518+ )
1519+ );
1520+ random.revealWithCallback (
1521+ provider1,
1522+ assignedSequenceNumber,
1523+ userRandomNumber,
1524+ provider1Proofs[assignedSequenceNumber]
1525+ );
1526+
1527+ // Verify request is cleared after successful reveal
1528+ EntropyStructs.Request memory reqAfterReveal = random.getRequest (
1529+ provider1,
1530+ assignedSequenceNumber
1531+ );
1532+ assertEq (reqAfterReveal.sequenceNumber, 0 );
1533+ }
14341534}
14351535
14361536contract EntropyConsumer is IEntropyConsumer {
@@ -1439,10 +1539,13 @@ contract EntropyConsumer is IEntropyConsumer {
14391539 address public entropy;
14401540 address public provider;
14411541 bool public reverts;
1542+ uint256 public gasUsed;
1543+ uint256 public targetGasUsage;
14421544
14431545 constructor (address _entropy , bool _reverts ) {
14441546 entropy = _entropy;
14451547 reverts = _reverts;
1548+ targetGasUsage = 0 ; // Default target
14461549 }
14471550
14481551 function requestEntropy (
@@ -1462,11 +1565,29 @@ contract EntropyConsumer is IEntropyConsumer {
14621565 reverts = _reverts;
14631566 }
14641567
1568+ function setTargetGasUsage (uint256 _targetGasUsage ) public {
1569+ targetGasUsage = _targetGasUsage;
1570+ }
1571+
14651572 function entropyCallback (
14661573 uint64 _sequence ,
14671574 address _provider ,
14681575 bytes32 _randomness
14691576 ) internal override {
1577+ uint256 startGas = gasleft ();
1578+ uint256 currentGasUsed = 0 ;
1579+
1580+ // Keep consuming gas until we reach our target
1581+ while (currentGasUsed < targetGasUsage) {
1582+ // Consume gas with a hash operation
1583+ bytes32 hash = keccak256 (
1584+ abi.encodePacked (currentGasUsed, _randomness)
1585+ );
1586+ currentGasUsed = startGas - gasleft ();
1587+ }
1588+
1589+ gasUsed = currentGasUsed;
1590+
14701591 if (! reverts) {
14711592 sequence = _sequence;
14721593 provider = _provider;
0 commit comments