Skip to content

Commit 57d5ae2

Browse files
committed
improve tests
1 parent 1fa8c14 commit 57d5ae2

File tree

4 files changed

+196
-88
lines changed

4 files changed

+196
-88
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,17 @@ abstract contract Entropy is IEntropy, EntropyState {
262262
// Set gasLimit10k to 0 to disable.
263263
req.gasLimit10k = 0;
264264
} else {
265-
req.gasLimit10k = roundGas(callbackGasLimit);
266-
}
265+
// This check does two important things:
266+
// 1. Providers have a minimum fee set for their defaultGasLimit. If users request less gas than that,
267+
// they still pay for the full gas limit. So we may as well give them the full limit here.
268+
// 2. If a provider has a defaultGasLimit != 0, we need to ensure that all requests have a >0 gas limit
269+
// so that we opt-in to the new callback failure state flow.
270+
req.gasLimit10k = roundGas(
271+
callbackGasLimit < providerInfo.defaultGasLimit
272+
? providerInfo.defaultGasLimit
273+
: callbackGasLimit
274+
);
275+
}
267276
}
268277

269278
// As a user, request a random number from `provider`. Prior to calling this method, the user should

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

Lines changed: 178 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1457,6 +1457,12 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
14571457
provider1
14581458
);
14591459
assertEq(info.defaultGasLimit, newGasLimit);
1460+
1461+
// Can reset back to 0.
1462+
vm.prank(provider1);
1463+
random.setDefaultGasLimit(0);
1464+
info = random.getProviderInfo(provider1);
1465+
assertEq(info.defaultGasLimit, 0);
14601466
}
14611467

14621468
function testSetDefaultGasLimitRevertIfNotFromProvider() public {
@@ -1509,136 +1515,210 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
15091515
assertEq(req.gasLimit10k, 20);
15101516
}
15111517

1512-
function testRequestWithCallbackAndGasLimitFeeScaling() public {
1513-
uint32 defaultGasLimit = 100000;
1514-
uint32 doubleGasLimit = 200000;
1515-
1518+
function testGasLimitsAndFeeRounding() public {
15161519
vm.prank(provider1);
1517-
random.setDefaultGasLimit(defaultGasLimit);
1520+
random.setDefaultGasLimit(20000);
1521+
vm.prank(provider1);
1522+
random.setProviderFee(1);
15181523

1519-
uint baseFee = random.getFee(provider1);
1520-
assertEq(baseFee, provider1FeeInWei + pythFeeInWei);
1524+
// Test exact multiples of 10,000
1525+
assertGasLimitAndFee(0, 2, 1);
1526+
assertGasLimitAndFee(10000, 2, 1);
1527+
assertGasLimitAndFee(20000, 2, 1);
1528+
assertGasLimitAndFee(100000, 10, 5);
15211529

1522-
// Fee scales proportionally with gas limit
1523-
uint scaledFee = random.getFeeForGas(provider1, doubleGasLimit);
1524-
assertEq(scaledFee, 2 * provider1FeeInWei + pythFeeInWei);
1525-
}
1530+
// Test values just below multiples of 10,000
1531+
assertGasLimitAndFee(9999, 2, 1);
1532+
assertGasLimitAndFee(19999, 2, 1);
1533+
assertGasLimitAndFee(39999, 4, 2);
1534+
assertGasLimitAndFee(99999, 10, 5);
15261535

1527-
function testRequestWithCallbackAndGasLimitInsufficientFee() public {
1528-
uint32 defaultGasLimit = 100000;
1529-
uint32 doubleGasLimit = 200000;
1536+
// Test values just above multiples of 10,000
1537+
assertGasLimitAndFee(10001, 2, 1);
1538+
assertGasLimitAndFee(20001, 3, 1);
1539+
assertGasLimitAndFee(100001, 11, 5);
1540+
assertGasLimitAndFee(110001, 12, 6);
1541+
1542+
// Test middle values
1543+
assertGasLimitAndFee(5000, 2, 1);
1544+
assertGasLimitAndFee(15000, 2, 1);
1545+
assertGasLimitAndFee(25000, 3, 1);
1546+
1547+
// Test maximum value
1548+
assertGasLimitAndFee(
1549+
uint32(type(uint16).max) * 10000,
1550+
type(uint16).max,
1551+
uint128(type(uint16).max) / 2
1552+
);
15301553

1554+
// A provider with a 0 gas limit is opted-out of the failure state flow, indicated by
1555+
// a 0 gas limit on all requests.
15311556
vm.prank(provider1);
1532-
random.setDefaultGasLimit(defaultGasLimit);
1557+
random.setDefaultGasLimit(0);
15331558

1559+
assertGasLimitAndFee(0, 0, 1);
1560+
assertGasLimitAndFee(10000, 0, 1);
1561+
assertGasLimitAndFee(20000, 0, 1);
1562+
assertGasLimitAndFee(100000, 0, 1);
1563+
}
1564+
1565+
// Helper method to create a request with a specific gas limit and check the gasLimit10k / provider fees
1566+
function assertGasLimitAndFee(
1567+
uint32 gasLimit,
1568+
uint16 expectedGasLimit10k,
1569+
uint128 expectedProviderFee
1570+
) internal {
1571+
// Create a request with callback
15341572
bytes32 userRandomNumber = bytes32(uint(42));
1535-
uint baseFee = random.getFee(provider1); // This is insufficient for double gas
1573+
uint fee = random.getFeeForGas(provider1, gasLimit);
1574+
assertEq(fee - random.getPythFee(), expectedProviderFee);
15361575

1537-
vm.deal(user1, baseFee);
1576+
// Passing 1 wei less than the expected fee causes a revert.
1577+
vm.deal(user1, fee);
15381578
vm.prank(user1);
15391579
vm.expectRevert(EntropyErrors.InsufficientFee.selector);
1540-
random.requestWithCallbackAndGasLimit{value: baseFee}(
1580+
random.requestWithCallbackAndGasLimit{value: fee - 1}(
15411581
provider1,
15421582
userRandomNumber,
1543-
doubleGasLimit
1583+
gasLimit
15441584
);
1545-
}
15461585

1547-
function testRequestWithCallbackAndGasLimitLowerThanDefault() public {
1548-
uint32 defaultGasLimit = 100000;
1549-
uint32 lowerGasLimit = 50000;
1550-
1551-
vm.prank(provider1);
1552-
random.setDefaultGasLimit(defaultGasLimit);
1553-
1554-
bytes32 userRandomNumber = bytes32(uint(42));
1555-
uint fee = random.getFeeForGas(provider1, lowerGasLimit);
1556-
1557-
vm.deal(user1, fee);
1586+
uint128 startingAccruedProviderFee = random
1587+
.getProviderInfo(provider1)
1588+
.accruedFeesInWei;
15581589
vm.prank(user1);
15591590
uint64 sequenceNumber = random.requestWithCallbackAndGasLimit{
15601591
value: fee
1561-
}(provider1, userRandomNumber, lowerGasLimit);
1592+
}(provider1, userRandomNumber, gasLimit);
15621593

1594+
assertEq(
1595+
random.getProviderInfo(provider1).accruedFeesInWei -
1596+
startingAccruedProviderFee,
1597+
expectedProviderFee
1598+
);
1599+
1600+
// Check the gasLimit10k field in the request
15631601
EntropyStructs.Request memory req = random.getRequest(
15641602
provider1,
15651603
sequenceNumber
15661604
);
1567-
assertEq(req.gasLimit10k, 5);
1568-
// Fee should be the same as base fee since we're using less gas than default
1569-
assertEq(fee, random.getFee(provider1));
1605+
assertEq(req.gasLimit10k, expectedGasLimit10k);
15701606
}
15711607

1572-
function testGasLimitsAndFeeRounding() public {
1573-
vm.prank(provider1);
1574-
random.setDefaultGasLimit(20000);
1608+
function testCallbackProvidedGas() public {
15751609
vm.prank(provider1);
1576-
random.setProviderFee(1);
1577-
1578-
// Test exact multiples of 10,000
1579-
assertGasLimit10k(0, 0, 1);
1580-
assertGasLimit10k(10000, 1, 1);
1581-
assertGasLimit10k(20000, 2, 1);
1582-
assertGasLimit10k(100000, 10, 5);
1610+
random.setDefaultGasLimit(200000);
15831611

1584-
// Test values just below multiples of 10,000
1585-
assertGasLimit10k(9999, 1, 1);
1586-
assertGasLimit10k(19999, 2, 1);
1587-
assertGasLimit10k(39999, 4, 2);
1588-
assertGasLimit10k(99999, 10, 5);
1612+
assertCallbackResult(0, 190000, true);
1613+
assertCallbackResult(0, 210000, false);
1614+
assertCallbackResult(300000, 290000, true);
1615+
assertCallbackResult(300000, 310000, false);
15891616

1590-
// Test values just above multiples of 10,000
1591-
assertGasLimit10k(10001, 2, 1);
1592-
assertGasLimit10k(20001, 3, 1);
1593-
assertGasLimit10k(100001, 11, 5);
1594-
assertGasLimit10k(110001, 12, 6);
1595-
1596-
// Test middle values
1597-
assertGasLimit10k(5000, 1, 1);
1598-
assertGasLimit10k(15000, 2, 1);
1599-
assertGasLimit10k(25000, 3, 1);
1617+
// A provider that hasn't upgraded to the callback failure flow
1618+
// can never cause a callback to fail because it runs out of gas.
1619+
vm.prank(provider1);
1620+
random.setDefaultGasLimit(0);
16001621

1601-
// Test maximum value
1602-
assertGasLimit10k(
1603-
uint32(type(uint16).max) * 10000,
1604-
type(uint16).max,
1605-
uint128(type(uint16).max) / 2
1606-
);
1622+
assertCallbackResult(0, 190000, true);
1623+
assertCallbackResult(0, 210000, true);
1624+
assertCallbackResult(300000, 290000, true);
1625+
assertCallbackResult(300000, 310000, true);
16071626
}
16081627

1609-
// Helper method to create a request with a specific gas limit and check the gasLimit10k field
1610-
function assertGasLimit10k(
1628+
// Helper method to assert whether a request with a specific gas limit / a callback with a specific gas cost
1629+
// should be successful or not.
1630+
function assertCallbackResult(
16111631
uint32 gasLimit,
1612-
uint16 expectedGasLimit10k,
1613-
uint128 expectedProviderFee
1632+
uint32 callbackGasUsage,
1633+
bool expectSuccess
16141634
) internal {
16151635
// Create a request with callback
16161636
bytes32 userRandomNumber = bytes32(uint(42));
16171637
uint fee = random.getFeeForGas(provider1, gasLimit);
1618-
assertEq(fee - random.getPythFee(), expectedProviderFee);
16191638

16201639
vm.deal(user1, fee);
16211640
vm.prank(user1);
1622-
uint64 sequenceNumber = random.requestWithCallbackAndGasLimit{
1623-
value: fee
1624-
}(provider1, userRandomNumber, gasLimit);
1641+
EntropyConsumer consumer = new EntropyConsumer(address(random), false);
1642+
uint64 sequenceNumber = consumer.requestEntropyWithGasLimit{value: fee}(
1643+
userRandomNumber,
1644+
gasLimit
1645+
);
1646+
1647+
consumer.setTargetGasUsage(callbackGasUsage);
16251648

1626-
// Check the gasLimit10k field in the request
16271649
EntropyStructs.Request memory req = random.getRequest(
16281650
provider1,
16291651
sequenceNumber
16301652
);
1631-
assertEq(req.gasLimit10k, expectedGasLimit10k);
1653+
1654+
if (!expectSuccess) {
1655+
vm.expectEmit(false, false, false, true, address(random));
1656+
emit CallbackFailed(
1657+
provider1,
1658+
address(consumer),
1659+
sequenceNumber,
1660+
userRandomNumber,
1661+
provider1Proofs[sequenceNumber],
1662+
random.combineRandomValues(
1663+
userRandomNumber,
1664+
provider1Proofs[sequenceNumber],
1665+
0
1666+
),
1667+
// out-of-gas reverts have an empty bytes array as the return value.
1668+
""
1669+
);
1670+
random.revealWithCallback(
1671+
provider1,
1672+
sequenceNumber,
1673+
userRandomNumber,
1674+
provider1Proofs[sequenceNumber]
1675+
);
1676+
1677+
// Verify request is still active after failure
1678+
EntropyStructs.Request memory reqAfterFailure = random.getRequest(
1679+
provider1,
1680+
sequenceNumber
1681+
);
1682+
assertEq(reqAfterFailure.sequenceNumber, sequenceNumber);
1683+
assertEq(
1684+
reqAfterFailure.callbackStatus,
1685+
EntropyStatusConstants.CALLBACK_FAILED
1686+
);
1687+
} else {
1688+
vm.expectEmit(false, false, false, true, address(random));
1689+
emit RevealedWithCallback(
1690+
req,
1691+
userRandomNumber,
1692+
provider1Proofs[sequenceNumber],
1693+
random.combineRandomValues(
1694+
userRandomNumber,
1695+
provider1Proofs[sequenceNumber],
1696+
0
1697+
)
1698+
);
1699+
random.revealWithCallback(
1700+
provider1,
1701+
sequenceNumber,
1702+
userRandomNumber,
1703+
provider1Proofs[sequenceNumber]
1704+
);
1705+
1706+
// Verify request is cleared after successful callback
1707+
EntropyStructs.Request memory reqAfterSuccess = random.getRequest(
1708+
provider1,
1709+
sequenceNumber
1710+
);
1711+
assertEq(reqAfterSuccess.sequenceNumber, 0);
1712+
}
16321713
}
16331714
}
16341715

16351716
contract EntropyConsumer is IEntropyConsumer {
16361717
uint64 public sequence;
16371718
bytes32 public randomness;
1638-
address public entropy;
16391719
address public provider;
1720+
address public entropy;
16401721
bool public reverts;
1641-
uint256 public gasUsed;
16421722
uint256 public targetGasUsage;
16431723

16441724
constructor(address _entropy, bool _reverts) {
@@ -1656,6 +1736,16 @@ contract EntropyConsumer is IEntropyConsumer {
16561736
}(_provider, randomNumber);
16571737
}
16581738

1739+
function requestEntropyWithGasLimit(
1740+
bytes32 randomNumber,
1741+
uint32 gasLimit
1742+
) public payable returns (uint64 sequenceNumber) {
1743+
address _provider = IEntropy(entropy).getDefaultProvider();
1744+
sequenceNumber = IEntropy(entropy).requestWithCallbackAndGasLimit{
1745+
value: msg.value
1746+
}(_provider, randomNumber, gasLimit);
1747+
}
1748+
16591749
function getEntropy() internal view override returns (address) {
16601750
return entropy;
16611751
}
@@ -1665,6 +1755,10 @@ contract EntropyConsumer is IEntropyConsumer {
16651755
}
16661756

16671757
function setTargetGasUsage(uint256 _targetGasUsage) public {
1758+
require(
1759+
_targetGasUsage > 60000,
1760+
"Target gas usage cannot be below 60k (~the cost of storing callback results)"
1761+
);
16681762
targetGasUsage = _targetGasUsage;
16691763
}
16701764

@@ -1674,22 +1768,20 @@ contract EntropyConsumer is IEntropyConsumer {
16741768
bytes32 _randomness
16751769
) internal override {
16761770
uint256 startGas = gasleft();
1677-
uint256 currentGasUsed = 0;
1771+
1772+
sequence = _sequence;
1773+
provider = _provider;
1774+
randomness = _randomness;
16781775

16791776
// Keep consuming gas until we reach our target
1777+
uint256 currentGasUsed = startGas - gasleft();
16801778
while (currentGasUsed < targetGasUsage) {
16811779
// Consume gas with a hash operation
16821780
keccak256(abi.encodePacked(currentGasUsed, _randomness));
16831781
currentGasUsed = startGas - gasleft();
16841782
}
16851783

1686-
gasUsed = currentGasUsed;
1687-
1688-
if (!reverts) {
1689-
sequence = _sequence;
1690-
provider = _provider;
1691-
randomness = _randomness;
1692-
} else {
1784+
if (reverts) {
16931785
revert("Callback failed");
16941786
}
16951787
}

target_chains/ethereum/entropy_sdk/solidity/EntropyErrors.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ library EntropyErrors {
4545
error UpdateTooOld();
4646
// Not enough gas was provided to the function to execute the callback with the desired amount of gas.
4747
error InsufficientGas();
48+
// An argument to the function call was invalid or out of the expected range.
49+
error InvalidArgument();
4850
}

target_chains/ethereum/entropy_sdk/solidity/abis/EntropyErrors.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
"name": "InsufficientGas",
2525
"type": "error"
2626
},
27+
{
28+
"inputs": [],
29+
"name": "InvalidArgument",
30+
"type": "error"
31+
},
2732
{
2833
"inputs": [],
2934
"name": "InvalidRevealCall",

0 commit comments

Comments
 (0)