Skip to content

Commit 90bc88d

Browse files
committed
cleaning up
1 parent 6e8898d commit 90bc88d

File tree

2 files changed

+78
-39
lines changed

2 files changed

+78
-39
lines changed

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

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -515,17 +515,20 @@ abstract contract Entropy is IEntropy, EntropyState {
515515
// any reverts will be reported as an event. Any failing requests move to a failure state
516516
// at which point they can be recovered. The recovery flow invokes the callback directly
517517
// (no catching errors) which allows callers to easily see the revert reason.
518-
if (req.gasLimit10k != 0 && req.callbackStatus == EntropyStatusConstants.CALLBACK_NOT_STARTED) {
518+
if (
519+
req.gasLimit10k != 0 &&
520+
req.callbackStatus == EntropyStatusConstants.CALLBACK_NOT_STARTED
521+
) {
519522
req.callbackStatus = EntropyStatusConstants.CALLBACK_IN_PROGRESS;
520523
bool success;
521524
bytes memory ret;
522525
uint256 startingGas = gasleft();
523526
(success, ret) = callAddress.excessivelySafeCall(
524527
// Warning: the provided gas limit below is only an *upper bound* on the gas provided to the call.
525-
// If the current context has less gas the desired value, 63/64ths of the current context's gas will
526-
// be provided instead. (See CALL opcode docs here https://www.evm.codes/?fork=cancun#f1)
528+
// At most 63/64ths of the current context's gas will be provided to a call, which may be less
529+
// than the indicated gas limit. (See CALL opcode docs here https://www.evm.codes/?fork=cancun#f1)
527530
// Consequently, out-of-gas reverts need to be handled carefully to ensure that the callback
528-
// was truly provided with a sufficient amount of gas.
531+
// was truly provided with a sufficient amount of gas.
529532
uint256(req.gasLimit10k) * 10000,
530533
256, // copy at most 256 bytes of the return value into ret.
531534
abi.encodeWithSelector(
@@ -535,7 +538,6 @@ abstract contract Entropy is IEntropy, EntropyState {
535538
randomNumber
536539
)
537540
);
538-
uint256 endingGas = gasleft();
539541
// Reset status to not started here in case the transaction reverts.
540542
req.callbackStatus = EntropyStatusConstants.CALLBACK_NOT_STARTED;
541543

@@ -547,10 +549,15 @@ abstract contract Entropy is IEntropy, EntropyState {
547549
randomNumber
548550
);
549551
clearRequest(provider, sequenceNumber);
550-
} else if (ret.length > 0 || startingGas - endingGas >= uint256(req.gasLimit10k) * 10000) {
552+
} else if (
553+
ret.length > 0 ||
554+
(startingGas * 31) / 32 > uint256(req.gasLimit10k) * 10000
555+
) {
551556
// The callback reverted for some reason.
552-
// If ret.length == 0, then the callback ran out of gas. In this case, ensure that the
553-
// callback was provided with sufficient gas.
557+
// If ret.length > 0, then we know the callback manually triggered a revert, so it's safe to mark it as failed.
558+
// If ret.length == 0, then the callback might have run out of gas (though there are other ways to trigger a revert with ret.length == 0).
559+
// In this case, ensure that the callback was provided with sufficient gas. Technically, 63/64ths of the startingGas is forwarded,
560+
// but we're using 31/32 to introduce a margin of safety.
554561
emit CallbackFailed(
555562
provider,
556563
req.requester,
@@ -562,11 +569,11 @@ abstract contract Entropy is IEntropy, EntropyState {
562569
);
563570
req.callbackStatus = EntropyStatusConstants.CALLBACK_FAILED;
564571
} else {
565-
// Callback reverted by running out of gas, but the calling context did not have enough gas
572+
// Callback reverted by (potentially) running out of gas, but the calling context did not have enough gas
566573
// to run the callback. This is a corner case that can happen due to the nuances of gas passing
567574
// in calls (see the comment on the call above).
568-
//
569-
// (Note that reverting here plays nicely with the estimateGas RPC method, which binary searches for
575+
//
576+
// (Note that reverting here plays nicely with the estimateGas RPC method, which binary searches for
570577
// the smallest gas value that causes the transaction to *succeed*. See https://github.com/ethereum/go-ethereum/pull/3587 )
571578
revert EntropyErrors.InsufficientGas();
572579
}
@@ -640,11 +647,15 @@ abstract contract Entropy is IEntropy, EntropyState {
640647
];
641648

642649
uint32 roundedGasLimit = uint32(roundGas(gasLimit)) * 10000;
643-
if (roundedGasLimit > provider.defaultGasLimit) {
650+
if (
651+
provider.defaultGasLimit > 0 &&
652+
roundedGasLimit > provider.defaultGasLimit
653+
) {
644654
// This calculation rounds down the fee, which means that users can get some gas in the callback for free.
645655
// However, the value of the free gas is < 1 wei, which is insignificant.
646-
uint128 additionalFee = ((roundedGasLimit - provider.defaultGasLimit) *
647-
provider.feeInWei) / provider.defaultGasLimit;
656+
uint128 additionalFee = ((roundedGasLimit -
657+
provider.defaultGasLimit) * provider.feeInWei) /
658+
provider.defaultGasLimit;
648659
return provider.feeInWei + additionalFee;
649660
} else {
650661
return provider.feeInWei;
@@ -777,9 +788,7 @@ abstract contract Entropy is IEntropy, EntropyState {
777788

778789
// Rounds the provided quantity of gas into units of 10k gas.
779790
// If gas is not evenly divisible by 10k, rounds up.
780-
function roundGas(
781-
uint32 gas
782-
) public pure returns (uint16) {
791+
function roundGas(uint32 gas) internal pure returns (uint16) {
783792
uint32 gas10k = gas / 10000;
784793
if (gas10k * 10000 < gas) {
785794
gas10k += 1;

target_chains/ethereum/contracts/forge-test/Entropy.t.sol

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,7 +1229,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
12291229
// Test the corner case caused by the CALL opcode passing at most 63/64ths of the current gas
12301230
// to the sub-call.
12311231
function testRequestWithCallbackUsingTooMuchGas2() public {
1232-
// With a 64M gas limit, we will pass ~63M gas to the callback (which is insufficient), but still
1232+
// With a 64M gas limit, we will pass ~63M gas to the callback (which is insufficient), but still
12331233
// have ~1M gas to execute code within the revealWithCallback method, which should be enough to
12341234
// run all of the logic subsequent to the excessivelySafeCall.
12351235
uint32 defaultGasLimit = 64000000;
@@ -1246,10 +1246,6 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
12461246
uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}(
12471247
userRandomNumber
12481248
);
1249-
EntropyStructs.Request memory req = random.getRequest(
1250-
provider1,
1251-
assignedSequenceNumber
1252-
);
12531249

12541250
// The transaction reverts if the provider does not provide enough gas to forward
12551251
// the gasLimit to the callback transaction.
@@ -1573,32 +1569,66 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
15731569
assertEq(fee, random.getFee(provider1));
15741570
}
15751571

1576-
function testRoundGas() public {
1577-
// TODO: test this with the full request/reveal flow and update the max value
1572+
function testGasLimitsAndFeeRounding() public {
1573+
vm.prank(provider1);
1574+
random.setDefaultGasLimit(20000);
1575+
vm.prank(provider1);
1576+
random.setProviderFee(1);
15781577

15791578
// Test exact multiples of 10,000
1580-
assertEq(random.roundGas(0), 0);
1581-
assertEq(random.roundGas(10000), 1);
1582-
assertEq(random.roundGas(20000), 2);
1583-
assertEq(random.roundGas(100000), 10);
1579+
assertGasLimit10k(0, 0, 1);
1580+
assertGasLimit10k(10000, 1, 1);
1581+
assertGasLimit10k(20000, 2, 1);
1582+
assertGasLimit10k(100000, 10, 5);
15841583

15851584
// Test values just below multiples of 10,000
1586-
assertEq(random.roundGas(9999), 1);
1587-
assertEq(random.roundGas(19999), 2);
1588-
assertEq(random.roundGas(99999), 10);
1585+
assertGasLimit10k(9999, 1, 1);
1586+
assertGasLimit10k(19999, 2, 1);
1587+
assertGasLimit10k(39999, 4, 2);
1588+
assertGasLimit10k(99999, 10, 5);
15891589

15901590
// Test values just above multiples of 10,000
1591-
assertEq(random.roundGas(10001), 2);
1592-
assertEq(random.roundGas(20001), 3);
1593-
assertEq(random.roundGas(100001), 11);
1591+
assertGasLimit10k(10001, 2, 1);
1592+
assertGasLimit10k(20001, 3, 1);
1593+
assertGasLimit10k(100001, 11, 5);
1594+
assertGasLimit10k(110001, 12, 6);
15941595

15951596
// Test middle values
1596-
assertEq(random.roundGas(5000), 1);
1597-
assertEq(random.roundGas(15000), 2);
1598-
assertEq(random.roundGas(25000), 3);
1597+
assertGasLimit10k(5000, 1, 1);
1598+
assertGasLimit10k(15000, 2, 1);
1599+
assertGasLimit10k(25000, 3, 1);
15991600

1600-
// Test maximum uint32 value
1601-
assertEq(random.roundGas(type(uint32).max), 429497); // (2^32 - 1) / 10000 rounded up
1601+
// Test maximum value
1602+
assertGasLimit10k(
1603+
uint32(type(uint16).max) * 10000,
1604+
type(uint16).max,
1605+
uint128(type(uint16).max) / 2
1606+
);
1607+
}
1608+
1609+
// Helper method to create a request with a specific gas limit and check the gasLimit10k field
1610+
function assertGasLimit10k(
1611+
uint32 gasLimit,
1612+
uint16 expectedGasLimit10k,
1613+
uint128 expectedProviderFee
1614+
) internal {
1615+
// Create a request with callback
1616+
bytes32 userRandomNumber = bytes32(uint(42));
1617+
uint fee = random.getFeeForGas(provider1, gasLimit);
1618+
assertEq(fee - random.getPythFee(), expectedProviderFee);
1619+
1620+
vm.deal(user1, fee);
1621+
vm.prank(user1);
1622+
uint64 sequenceNumber = random.requestWithCallbackAndGasLimit{
1623+
value: fee
1624+
}(provider1, userRandomNumber, gasLimit);
1625+
1626+
// Check the gasLimit10k field in the request
1627+
EntropyStructs.Request memory req = random.getRequest(
1628+
provider1,
1629+
sequenceNumber
1630+
);
1631+
assertEq(req.gasLimit10k, expectedGasLimit10k);
16021632
}
16031633
}
16041634

0 commit comments

Comments
 (0)