-
Notifications
You must be signed in to change notification settings - Fork 306
feat(entropy): Better tester contract #2679
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,53 +3,292 @@ | |
| pragma solidity ^0.8.0; | ||
|
|
||
| import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; | ||
| import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; | ||
| import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; | ||
|
|
||
| // Dummy contract for testing Fortuna service under heavy load | ||
| // This contract will request many random numbers from the Entropy contract in a single transaction | ||
| // It also reverts on some of the callbacks for testing the retry mechanism | ||
| /** | ||
| * @title EntropyTester | ||
| * @notice A test contract for invoking entropy requests with configurable callback conditions | ||
| * @dev Supports both V1 and V2 entropy requests with configurable callback behavior | ||
| */ | ||
| contract EntropyTester is IEntropyConsumer { | ||
| IEntropy entropy; | ||
| mapping(uint64 => bool) public shouldRevert; | ||
| address public defaultEntropy; | ||
|
|
||
| constructor(address entropyAddress) { | ||
| entropy = IEntropy(entropyAddress); | ||
| // use callbackKey method to create the key of this mapping | ||
| mapping(bytes32 => CallbackData) public callbackData; | ||
|
|
||
| constructor(address _entropy) { | ||
| defaultEntropy = _entropy; | ||
| } | ||
|
|
||
| function batchRequests( | ||
| address provider, | ||
| uint64 successCount, | ||
| uint64 revertCount | ||
| ) public payable { | ||
| /** | ||
| * @notice Makes a V1 entropy request using default entropy and provider addresses | ||
| * @param callbackReverts Whether the callback should revert | ||
| * @param callbackGasUsage Amount of gas the callback should consume | ||
| * @return sequenceNumber The sequence number of the request | ||
| */ | ||
| function requestV1( | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage | ||
| ) public payable returns (uint64 sequenceNumber) { | ||
| sequenceNumber = requestV1( | ||
| address(0), | ||
| address(0), | ||
| callbackReverts, | ||
| callbackGasUsage | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Makes a V1 entropy request with specified entropy and provider addresses | ||
| * @param _entropy Address of the entropy contract (uses default if address(0)) | ||
| * @param _provider Address of the provider (uses default if address(0)) | ||
| * @param callbackReverts Whether the callback should revert | ||
| * @param callbackGasUsage Amount of gas the callback should consume | ||
| * @return sequenceNumber The sequence number of the request | ||
| */ | ||
| function requestV1( | ||
| address _entropy, | ||
| address _provider, | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage | ||
| ) public payable returns (uint64 sequenceNumber) { | ||
| requireGasUsageAboveLimit(callbackGasUsage); | ||
|
|
||
| IEntropy entropy = IEntropy(address(getEntropyWithDefault(_entropy))); | ||
| address provider = getProviderWithDefault(entropy, _provider); | ||
|
|
||
| uint128 fee = entropy.getFee(provider); | ||
| bytes32 zero; | ||
| for (uint64 i = 0; i < successCount; i++) { | ||
| uint64 seqNum = entropy.requestWithCallback{value: fee}( | ||
| provider, | ||
| zero | ||
| ); | ||
| shouldRevert[seqNum] = false; | ||
| sequenceNumber = entropy.requestWithCallback{value: fee}( | ||
| provider, | ||
| // Hardcoding the user contribution because we don't really care for testing the callback. | ||
| // Real users should pass this value in as an argument from the calling function. | ||
| bytes32(uint256(12345)) | ||
| ); | ||
|
|
||
| callbackData[ | ||
| callbackKey(address(entropy), provider, sequenceNumber) | ||
| ] = CallbackData(callbackReverts, callbackGasUsage); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Makes multiple V1 entropy requests in a batch | ||
| * @param _entropy Address of the entropy contract (uses default if address(0)) | ||
| * @param _provider Address of the provider (uses default if address(0)) | ||
| * @param callbackReverts Whether the callbacks should revert | ||
| * @param callbackGasUsage Amount of gas each callback should consume | ||
| * @param count Number of requests to make | ||
| */ | ||
| function batchRequestV1( | ||
| address _entropy, | ||
| address _provider, | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage, | ||
| uint32 count | ||
| ) public payable { | ||
| for (uint64 i = 0; i < count; i++) { | ||
| requestV1(_entropy, _provider, callbackReverts, callbackGasUsage); | ||
| } | ||
| for (uint64 i = 0; i < revertCount; i++) { | ||
| uint64 seqNum = entropy.requestWithCallback{value: fee}( | ||
| provider, | ||
| zero | ||
| } | ||
|
|
||
| /** | ||
| * @notice Makes a V2 entropy request using default entropy and provider addresses | ||
| * @param gasLimit Gas limit for the callback | ||
| * @param callbackReverts Whether the callback should revert | ||
| * @param callbackGasUsage Amount of gas the callback should consume | ||
| * @return sequenceNumber The sequence number of the request | ||
| */ | ||
| function requestV2( | ||
| uint32 gasLimit, | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage | ||
| ) public payable returns (uint64 sequenceNumber) { | ||
| sequenceNumber = requestV2( | ||
| address(0), | ||
| address(0), | ||
| gasLimit, | ||
| callbackReverts, | ||
| callbackGasUsage | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Makes a V2 entropy request with specified entropy and provider addresses | ||
| * @param _entropy Address of the entropy contract (uses default if address(0)) | ||
| * @param _provider Address of the provider (uses default if address(0)) | ||
| * @param gasLimit Gas limit for the callback | ||
| * @param callbackReverts Whether the callback should revert | ||
| * @param callbackGasUsage Amount of gas the callback should consume | ||
| * @return sequenceNumber The sequence number of the request | ||
| */ | ||
| function requestV2( | ||
| address _entropy, | ||
| address _provider, | ||
| uint32 gasLimit, | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage | ||
| ) public payable returns (uint64 sequenceNumber) { | ||
| requireGasUsageAboveLimit(callbackGasUsage); | ||
|
|
||
| IEntropyV2 entropy = getEntropyWithDefault(_entropy); | ||
| address provider = getProviderWithDefault(entropy, _provider); | ||
|
|
||
| uint128 fee = entropy.getFeeV2(provider, gasLimit); | ||
| sequenceNumber = entropy.requestV2{value: fee}(provider, gasLimit); | ||
|
|
||
| callbackData[ | ||
| callbackKey(address(entropy), provider, sequenceNumber) | ||
| ] = CallbackData(callbackReverts, callbackGasUsage); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Makes multiple V2 entropy requests in a batch | ||
| * @param _entropy Address of the entropy contract (uses default if address(0)) | ||
| * @param _provider Address of the provider (uses default if address(0)) | ||
| * @param gasLimit Gas limit for the callbacks | ||
| * @param callbackReverts Whether the callbacks should revert | ||
| * @param callbackGasUsage Amount of gas each callback should consume | ||
| * @param count Number of requests to make | ||
| */ | ||
| function batchRequestV2( | ||
| address _entropy, | ||
| address _provider, | ||
| uint32 gasLimit, | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage, | ||
| uint32 count | ||
| ) public payable { | ||
| for (uint64 i = 0; i < count; i++) { | ||
| requestV2( | ||
| _entropy, | ||
| _provider, | ||
| gasLimit, | ||
| callbackReverts, | ||
| callbackGasUsage | ||
| ); | ||
| shouldRevert[seqNum] = true; | ||
| } | ||
| } | ||
|
|
||
| function getEntropy() internal view override returns (address) { | ||
| return address(entropy); | ||
| /** | ||
| * @notice Modifies the callback behavior for an existing request | ||
| * @param _entropy Address of the entropy contract (uses default if address(0)) | ||
| * @param _provider Address of the provider (uses default if address(0)) | ||
| * @param sequenceNumber Sequence number of the request to modify | ||
| * @param callbackReverts Whether the callback should revert | ||
| * @param callbackGasUsage Amount of gas the callback should consume | ||
| */ | ||
| function modifyCallback( | ||
| address _entropy, | ||
| address _provider, | ||
| uint64 sequenceNumber, | ||
| bool callbackReverts, | ||
| uint32 callbackGasUsage | ||
| ) public { | ||
| requireGasUsageAboveLimit(callbackGasUsage); | ||
|
|
||
| IEntropyV2 entropy = getEntropyWithDefault(_entropy); | ||
| address provider = getProviderWithDefault(entropy, _provider); | ||
|
|
||
| callbackData[ | ||
| callbackKey(address(entropy), provider, sequenceNumber) | ||
| ] = CallbackData(callbackReverts, callbackGasUsage); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Callback function that gets called by the entropy contract | ||
| * @dev Implements IEntropyConsumer interface | ||
| * @param _sequence Sequence number of the request | ||
| * @param _provider Address of the provider | ||
| * @param _randomness The random value provided by the entropy contract | ||
| */ | ||
| function entropyCallback( | ||
| uint64 sequence, | ||
| address, //provider | ||
| bytes32 //randomNumber | ||
| ) internal view override { | ||
| if (shouldRevert[sequence]) { | ||
| revert("Reverting"); | ||
| uint64 _sequence, | ||
| address _provider, | ||
| bytes32 _randomness | ||
| ) internal override { | ||
| uint256 startGas = gasleft(); | ||
|
|
||
| bytes32 key = callbackKey(msg.sender, _provider, _sequence); | ||
| CallbackData memory callback = callbackData[key]; | ||
| delete callbackData[key]; | ||
|
|
||
| // Keep consuming gas until we reach our target | ||
| uint256 currentGasUsed = startGas - gasleft(); | ||
| while (currentGasUsed < callback.gasUsage) { | ||
| // Consume gas with a hash operation | ||
| keccak256(abi.encodePacked(currentGasUsed, _randomness)); | ||
| currentGasUsed = startGas - gasleft(); | ||
| } | ||
|
|
||
| if (callback.reverts) { | ||
| revert("Callback failed"); | ||
| } | ||
| } | ||
|
|
||
| function getEntropy() internal view override returns (address) { | ||
| return defaultEntropy; | ||
| } | ||
|
|
||
| /** | ||
| * @notice Gets the entropy contract address, using default if none specified | ||
| * @param _entropy Address of the entropy contract (uses default if address(0)) | ||
| * @return entropy The entropy contract interface | ||
| */ | ||
| function getEntropyWithDefault( | ||
| address _entropy | ||
| ) internal view returns (IEntropyV2 entropy) { | ||
| if (_entropy != address(0)) { | ||
| entropy = IEntropyV2(_entropy); | ||
| } else { | ||
| entropy = IEntropyV2(defaultEntropy); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @notice Gets the provider address, using default if none specified | ||
| * @param entropy The entropy contract interface | ||
| * @param _provider Address of the provider (uses default if address(0)) | ||
| * @return provider The provider address | ||
| */ | ||
| function getProviderWithDefault( | ||
| IEntropyV2 entropy, | ||
| address _provider | ||
| ) internal view returns (address provider) { | ||
| if (_provider == address(0)) { | ||
| provider = entropy.getDefaultProvider(); | ||
| } else { | ||
| provider = _provider; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * @notice Ensures the gas usage is above the minimum required limit | ||
| * @param gasUsage The gas usage to check | ||
| */ | ||
| function requireGasUsageAboveLimit(uint32 gasUsage) internal pure { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what's this? we are not storing callback results in this contract 🤔 |
||
| require( | ||
| gasUsage > 60000, | ||
| "Target gas usage cannot be below 60k (~the cost of storing callback results)" | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * @notice Generates a unique key for callback data storage | ||
| * @param entropy Address of the entropy contract | ||
| * @param provider Address of the provider | ||
| * @param sequenceNumber Sequence number of the request | ||
| * @return key The unique key for the callback data | ||
| */ | ||
| function callbackKey( | ||
| address entropy, | ||
| address provider, | ||
| uint64 sequenceNumber | ||
| ) internal pure returns (bytes32 key) { | ||
| key = keccak256(abi.encodePacked(entropy, provider, sequenceNumber)); | ||
| } | ||
|
|
||
| struct CallbackData { | ||
| bool reverts; | ||
| uint32 gasUsage; | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks a bit awkward. Maybe make
getEntropyWithDefaultjust return the address?