Skip to content

Commit 3c49ba3

Browse files
authored
feat(entropy): Better tester contract (#2679)
* delete old tester * doc the tester class * apache * gr * refactor
1 parent f9f8dd9 commit 3c49ba3

File tree

1 file changed

+271
-32
lines changed

1 file changed

+271
-32
lines changed

target_chains/ethereum/contracts/contracts/entropy/EntropyTester.sol

Lines changed: 271 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,292 @@
33
pragma solidity ^0.8.0;
44

55
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
6+
import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol";
67
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
78

8-
// Dummy contract for testing Fortuna service under heavy load
9-
// This contract will request many random numbers from the Entropy contract in a single transaction
10-
// It also reverts on some of the callbacks for testing the retry mechanism
9+
/**
10+
* @title EntropyTester
11+
* @notice A test contract for invoking entropy requests with configurable callback conditions
12+
* @dev Supports both V1 and V2 entropy requests with configurable callback behavior
13+
*/
1114
contract EntropyTester is IEntropyConsumer {
12-
IEntropy entropy;
13-
mapping(uint64 => bool) public shouldRevert;
15+
address public defaultEntropy;
1416

15-
constructor(address entropyAddress) {
16-
entropy = IEntropy(entropyAddress);
17+
// use callbackKey method to create the key of this mapping
18+
mapping(bytes32 => CallbackData) public callbackData;
19+
20+
constructor(address _entropy) {
21+
defaultEntropy = _entropy;
1722
}
1823

19-
function batchRequests(
20-
address provider,
21-
uint64 successCount,
22-
uint64 revertCount
23-
) public payable {
24+
/**
25+
* @notice Makes a V1 entropy request using default entropy and provider addresses
26+
* @param callbackReverts Whether the callback should revert
27+
* @param callbackGasUsage Amount of gas the callback should consume
28+
* @return sequenceNumber The sequence number of the request
29+
*/
30+
function requestV1(
31+
bool callbackReverts,
32+
uint32 callbackGasUsage
33+
) public payable returns (uint64 sequenceNumber) {
34+
sequenceNumber = requestV1(
35+
address(0),
36+
address(0),
37+
callbackReverts,
38+
callbackGasUsage
39+
);
40+
}
41+
42+
/**
43+
* @notice Makes a V1 entropy request with specified entropy and provider addresses
44+
* @param _entropy Address of the entropy contract (uses default if address(0))
45+
* @param _provider Address of the provider (uses default if address(0))
46+
* @param callbackReverts Whether the callback should revert
47+
* @param callbackGasUsage Amount of gas the callback should consume
48+
* @return sequenceNumber The sequence number of the request
49+
*/
50+
function requestV1(
51+
address _entropy,
52+
address _provider,
53+
bool callbackReverts,
54+
uint32 callbackGasUsage
55+
) public payable returns (uint64 sequenceNumber) {
56+
requireGasUsageAboveLimit(callbackGasUsage);
57+
58+
IEntropy entropy = getEntropyWithDefault(_entropy);
59+
address provider = getProviderWithDefault(entropy, _provider);
60+
2461
uint128 fee = entropy.getFee(provider);
25-
bytes32 zero;
26-
for (uint64 i = 0; i < successCount; i++) {
27-
uint64 seqNum = entropy.requestWithCallback{value: fee}(
28-
provider,
29-
zero
30-
);
31-
shouldRevert[seqNum] = false;
62+
sequenceNumber = entropy.requestWithCallback{value: fee}(
63+
provider,
64+
// Hardcoding the user contribution because we don't really care for testing the callback.
65+
// Real users should pass this value in as an argument from the calling function.
66+
bytes32(uint256(12345))
67+
);
68+
69+
callbackData[
70+
callbackKey(address(entropy), provider, sequenceNumber)
71+
] = CallbackData(callbackReverts, callbackGasUsage);
72+
}
73+
74+
/**
75+
* @notice Makes multiple V1 entropy requests in a batch
76+
* @param _entropy Address of the entropy contract (uses default if address(0))
77+
* @param _provider Address of the provider (uses default if address(0))
78+
* @param callbackReverts Whether the callbacks should revert
79+
* @param callbackGasUsage Amount of gas each callback should consume
80+
* @param count Number of requests to make
81+
*/
82+
function batchRequestV1(
83+
address _entropy,
84+
address _provider,
85+
bool callbackReverts,
86+
uint32 callbackGasUsage,
87+
uint32 count
88+
) public payable {
89+
for (uint64 i = 0; i < count; i++) {
90+
requestV1(_entropy, _provider, callbackReverts, callbackGasUsage);
3291
}
33-
for (uint64 i = 0; i < revertCount; i++) {
34-
uint64 seqNum = entropy.requestWithCallback{value: fee}(
35-
provider,
36-
zero
92+
}
93+
94+
/**
95+
* @notice Makes a V2 entropy request using default entropy and provider addresses
96+
* @param gasLimit Gas limit for the callback
97+
* @param callbackReverts Whether the callback should revert
98+
* @param callbackGasUsage Amount of gas the callback should consume
99+
* @return sequenceNumber The sequence number of the request
100+
*/
101+
function requestV2(
102+
uint32 gasLimit,
103+
bool callbackReverts,
104+
uint32 callbackGasUsage
105+
) public payable returns (uint64 sequenceNumber) {
106+
sequenceNumber = requestV2(
107+
address(0),
108+
address(0),
109+
gasLimit,
110+
callbackReverts,
111+
callbackGasUsage
112+
);
113+
}
114+
115+
/**
116+
* @notice Makes a V2 entropy request with specified entropy and provider addresses
117+
* @param _entropy Address of the entropy contract (uses default if address(0))
118+
* @param _provider Address of the provider (uses default if address(0))
119+
* @param gasLimit Gas limit for the callback
120+
* @param callbackReverts Whether the callback should revert
121+
* @param callbackGasUsage Amount of gas the callback should consume
122+
* @return sequenceNumber The sequence number of the request
123+
*/
124+
function requestV2(
125+
address _entropy,
126+
address _provider,
127+
uint32 gasLimit,
128+
bool callbackReverts,
129+
uint32 callbackGasUsage
130+
) public payable returns (uint64 sequenceNumber) {
131+
requireGasUsageAboveLimit(callbackGasUsage);
132+
133+
IEntropy entropy = getEntropyWithDefault(_entropy);
134+
address provider = getProviderWithDefault(entropy, _provider);
135+
136+
uint128 fee = entropy.getFeeV2(provider, gasLimit);
137+
sequenceNumber = entropy.requestV2{value: fee}(provider, gasLimit);
138+
139+
callbackData[
140+
callbackKey(address(entropy), provider, sequenceNumber)
141+
] = CallbackData(callbackReverts, callbackGasUsage);
142+
}
143+
144+
/**
145+
* @notice Makes multiple V2 entropy requests in a batch
146+
* @param _entropy Address of the entropy contract (uses default if address(0))
147+
* @param _provider Address of the provider (uses default if address(0))
148+
* @param gasLimit Gas limit for the callbacks
149+
* @param callbackReverts Whether the callbacks should revert
150+
* @param callbackGasUsage Amount of gas each callback should consume
151+
* @param count Number of requests to make
152+
*/
153+
function batchRequestV2(
154+
address _entropy,
155+
address _provider,
156+
uint32 gasLimit,
157+
bool callbackReverts,
158+
uint32 callbackGasUsage,
159+
uint32 count
160+
) public payable {
161+
for (uint64 i = 0; i < count; i++) {
162+
requestV2(
163+
_entropy,
164+
_provider,
165+
gasLimit,
166+
callbackReverts,
167+
callbackGasUsage
37168
);
38-
shouldRevert[seqNum] = true;
39169
}
40170
}
41171

42-
function getEntropy() internal view override returns (address) {
43-
return address(entropy);
172+
/**
173+
* @notice Modifies the callback behavior for an existing request
174+
* @param _entropy Address of the entropy contract (uses default if address(0))
175+
* @param _provider Address of the provider (uses default if address(0))
176+
* @param sequenceNumber Sequence number of the request to modify
177+
* @param callbackReverts Whether the callback should revert
178+
* @param callbackGasUsage Amount of gas the callback should consume
179+
*/
180+
function modifyCallback(
181+
address _entropy,
182+
address _provider,
183+
uint64 sequenceNumber,
184+
bool callbackReverts,
185+
uint32 callbackGasUsage
186+
) public {
187+
requireGasUsageAboveLimit(callbackGasUsage);
188+
189+
IEntropy entropy = getEntropyWithDefault(_entropy);
190+
address provider = getProviderWithDefault(entropy, _provider);
191+
192+
callbackData[
193+
callbackKey(address(entropy), provider, sequenceNumber)
194+
] = CallbackData(callbackReverts, callbackGasUsage);
44195
}
45196

197+
/**
198+
* @notice Callback function that gets called by the entropy contract
199+
* @dev Implements IEntropyConsumer interface
200+
* @param _sequence Sequence number of the request
201+
* @param _provider Address of the provider
202+
* @param _randomness The random value provided by the entropy contract
203+
*/
46204
function entropyCallback(
47-
uint64 sequence,
48-
address, //provider
49-
bytes32 //randomNumber
50-
) internal view override {
51-
if (shouldRevert[sequence]) {
52-
revert("Reverting");
205+
uint64 _sequence,
206+
address _provider,
207+
bytes32 _randomness
208+
) internal override {
209+
uint256 startGas = gasleft();
210+
211+
bytes32 key = callbackKey(msg.sender, _provider, _sequence);
212+
CallbackData memory callback = callbackData[key];
213+
delete callbackData[key];
214+
215+
// Keep consuming gas until we reach our target
216+
uint256 currentGasUsed = startGas - gasleft();
217+
while (currentGasUsed < callback.gasUsage) {
218+
// Consume gas with a hash operation
219+
keccak256(abi.encodePacked(currentGasUsed, _randomness));
220+
currentGasUsed = startGas - gasleft();
221+
}
222+
223+
if (callback.reverts) {
224+
revert("Callback failed");
225+
}
226+
}
227+
228+
function getEntropy() internal view override returns (address) {
229+
return defaultEntropy;
230+
}
231+
232+
/**
233+
* @notice Gets the entropy contract address, using default if none specified
234+
* @param _entropy Address of the entropy contract (uses default if address(0))
235+
* @return entropy The entropy contract interface
236+
*/
237+
function getEntropyWithDefault(
238+
address _entropy
239+
) internal view returns (IEntropy entropy) {
240+
if (_entropy != address(0)) {
241+
entropy = IEntropy(_entropy);
242+
} else {
243+
entropy = IEntropy(defaultEntropy);
244+
}
245+
}
246+
247+
/**
248+
* @notice Gets the provider address, using default if none specified
249+
* @param entropy The entropy contract interface
250+
* @param _provider Address of the provider (uses default if address(0))
251+
* @return provider The provider address
252+
*/
253+
function getProviderWithDefault(
254+
IEntropy entropy,
255+
address _provider
256+
) internal view returns (address provider) {
257+
if (_provider == address(0)) {
258+
provider = entropy.getDefaultProvider();
259+
} else {
260+
provider = _provider;
53261
}
54262
}
263+
264+
/**
265+
* @notice Ensures the gas usage is above the minimum required limit
266+
* @param gasUsage The gas usage to check
267+
*/
268+
function requireGasUsageAboveLimit(uint32 gasUsage) internal pure {
269+
require(
270+
gasUsage > 60000,
271+
"Target gas usage cannot be below 60k (~upper bound on necessary callback operations)"
272+
);
273+
}
274+
275+
/**
276+
* @notice Generates a unique key for callback data storage
277+
* @param entropy Address of the entropy contract
278+
* @param provider Address of the provider
279+
* @param sequenceNumber Sequence number of the request
280+
* @return key The unique key for the callback data
281+
*/
282+
function callbackKey(
283+
address entropy,
284+
address provider,
285+
uint64 sequenceNumber
286+
) internal pure returns (bytes32 key) {
287+
key = keccak256(abi.encodePacked(entropy, provider, sequenceNumber));
288+
}
289+
290+
struct CallbackData {
291+
bool reverts;
292+
uint32 gasUsage;
293+
}
55294
}

0 commit comments

Comments
 (0)