diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 61174df6e5..9321483a36 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2351,6 +2351,9 @@ importers: '@matterlabs/hardhat-zksync-solc': specifier: ^0.3.14 version: 0.3.17(encoding@0.1.13)(hardhat@2.22.19(bufferutil@4.0.7)(ts-node@10.9.2(@types/node@22.14.0)(typescript@5.8.2))(typescript@5.8.2)(utf-8-validate@6.0.3)) + '@nomad-xyz/excessively-safe-call': + specifier: ^0.0.1-rc.1 + version: 0.0.1-rc.1 '@nomiclabs/hardhat-etherscan': specifier: ^3.1.7 version: 3.1.8(hardhat@2.22.19(bufferutil@4.0.7)(ts-node@10.9.2(@types/node@22.14.0)(typescript@5.8.2))(typescript@5.8.2)(utf-8-validate@6.0.3)) @@ -6085,6 +6088,9 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@nomad-xyz/excessively-safe-call@0.0.1-rc.1': + resolution: {integrity: sha512-Q5GVakBy8J1kWjydH6W5LNbkYY+Cw2doBiLodOfbFGujeng6zM+EtMLb/V+vkWbskbM81y2r+LG5NmxsxyElPA==} + '@nomicfoundation/edr-darwin-arm64@0.8.0': resolution: {integrity: sha512-sKTmOu/P5YYhxT0ThN2Pe3hmCE/5Ag6K/eYoiavjLWbR7HEb5ZwPu2rC3DpuUk1H+UKJqt7o4/xIgJxqw9wu6A==} engines: {node: '>= 18'} @@ -28245,6 +28251,8 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@nomad-xyz/excessively-safe-call@0.0.1-rc.1': {} + '@nomicfoundation/edr-darwin-arm64@0.8.0': {} '@nomicfoundation/edr-darwin-x64@0.8.0': {} diff --git a/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol b/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol index ede2e39976..589fa7eeee 100644 --- a/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol +++ b/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol @@ -8,7 +8,9 @@ import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol"; import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "@nomad-xyz/excessively-safe-call/src/ExcessivelySafeCall.sol"; import "./EntropyState.sol"; +import "@pythnetwork/entropy-sdk-solidity/EntropyStatusConstants.sol"; // Entropy implements a secure 2-party random number generation procedure. The protocol // is an extension of a simple commit/reveal protocol. The original version has the following steps: @@ -76,6 +78,8 @@ import "./EntropyState.sol"; // the user is always incentivized to reveal their random number, and that the protocol has an escape hatch for // cases where the user chooses not to reveal. abstract contract Entropy is IEntropy, EntropyState { + using ExcessivelySafeCall for address; + function _initialize( address admin, uint128 pythFeeInWei, @@ -247,7 +251,9 @@ abstract contract Entropy is IEntropy, EntropyState { req.blockNumber = SafeCast.toUint64(block.number); req.useBlockhash = useBlockhash; - req.isRequestWithCallback = isRequestWithCallback; + req.callbackStatus = isRequestWithCallback + ? EntropyStatusConstants.CALLBACK_NOT_STARTED + : EntropyStatusConstants.CALLBACK_NOT_NECESSARY; } // As a user, request a random number from `provider`. Prior to calling this method, the user should @@ -403,7 +409,7 @@ abstract contract Entropy is IEntropy, EntropyState { } // Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof - // against the corresponding commitments in the in-flight request. If both values are validated, this function returns + // against the corresponding commitments in the in-flight request. If both values are validated, this method returns // the corresponding random number. // // Note that this function can only be called once per in-flight request. Calling this function deletes the stored @@ -423,7 +429,9 @@ abstract contract Entropy is IEntropy, EntropyState { sequenceNumber ); - if (req.isRequestWithCallback) { + if ( + req.callbackStatus != EntropyStatusConstants.CALLBACK_NOT_NECESSARY + ) { revert EntropyErrors.InvalidRevealCall(); } @@ -467,9 +475,14 @@ abstract contract Entropy is IEntropy, EntropyState { sequenceNumber ); - if (!req.isRequestWithCallback) { + if ( + !(req.callbackStatus == + EntropyStatusConstants.CALLBACK_NOT_STARTED || + req.callbackStatus == EntropyStatusConstants.CALLBACK_FAILED) + ) { revert EntropyErrors.InvalidRevealCall(); } + bytes32 blockHash; bytes32 randomNumber; (randomNumber, blockHash) = revealHelper( @@ -480,26 +493,75 @@ abstract contract Entropy is IEntropy, EntropyState { address callAddress = req.requester; - emit RevealedWithCallback( - req, - userRandomNumber, - providerRevelation, - randomNumber - ); - - clearRequest(provider, sequenceNumber); - - // Check if the callAddress is a contract account. - uint len; - assembly { - len := extcodesize(callAddress) - } - if (len != 0) { - IEntropyConsumer(callAddress)._entropyCallback( - sequenceNumber, - provider, + // Requests that haven't been invoked yet will be invoked safely (catching reverts), and + // any reverts will be reported as an event. Any failing requests move to a failure state + // at which point they can be recovered. The recovery flow invokes the callback directly + // (no catching errors) which allows callers to easily see the revert reason. + if (req.callbackStatus == EntropyStatusConstants.CALLBACK_NOT_STARTED) { + req.callbackStatus = EntropyStatusConstants.CALLBACK_IN_PROGRESS; + bool success; + bytes memory ret; + (success, ret) = callAddress.excessivelySafeCall( + gasleft(), // TODO: providers need to be able to configure this in the future. + 256, // copy at most 256 bytes of the return value into ret. + abi.encodeWithSelector( + IEntropyConsumer._entropyCallback.selector, + sequenceNumber, + provider, + randomNumber + ) + ); + // Reset status to not started here in case the transaction reverts. + req.callbackStatus = EntropyStatusConstants.CALLBACK_NOT_STARTED; + + if (success) { + emit RevealedWithCallback( + req, + userRandomNumber, + providerRevelation, + randomNumber + ); + clearRequest(provider, sequenceNumber); + } else if (ret.length > 0) { + // Callback reverted for some reason that is *not* out-of-gas. + emit CallbackFailed( + provider, + req.requester, + sequenceNumber, + userRandomNumber, + providerRevelation, + randomNumber, + ret + ); + req.callbackStatus = EntropyStatusConstants.CALLBACK_FAILED; + } else { + // The callback ran out of gas + // TODO: this case will go away once we add provider gas limits, so we're not putting in a custom error type. + require(false, "provider needs to send more gas"); + } + } else { + // This case uses the checks-effects-interactions pattern to avoid reentry attacks + emit RevealedWithCallback( + req, + userRandomNumber, + providerRevelation, randomNumber ); + + clearRequest(provider, sequenceNumber); + + // Check if the callAddress is a contract account. + uint len; + assembly { + len := extcodesize(callAddress) + } + if (len != 0) { + IEntropyConsumer(callAddress)._entropyCallback( + sequenceNumber, + provider, + randomNumber + ); + } } } diff --git a/target_chains/ethereum/contracts/forge-test/Entropy.t.sol b/target_chains/ethereum/contracts/forge-test/Entropy.t.sol index d088bb0c37..720fd092d0 100644 --- a/target_chains/ethereum/contracts/forge-test/Entropy.t.sol +++ b/target_chains/ethereum/contracts/forge-test/Entropy.t.sol @@ -10,6 +10,7 @@ import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import "./utils/EntropyTestUtils.t.sol"; import "../contracts/entropy/EntropyUpgradable.sol"; +import "@pythnetwork/entropy-sdk-solidity/EntropyStatusConstants.sol"; // TODO // - fuzz test? @@ -804,7 +805,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents { blockNumber: 1234, requester: user1, useBlockhash: false, - isRequestWithCallback: true + callbackStatus: EntropyStatusConstants.CALLBACK_NOT_STARTED }) ); vm.roll(1234); @@ -835,7 +836,7 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents { function testRequestWithCallbackAndRevealWithCallbackByContract() public { bytes32 userRandomNumber = bytes32(uint(42)); uint fee = random.getFee(provider1); - EntropyConsumer consumer = new EntropyConsumer(address(random)); + EntropyConsumer consumer = new EntropyConsumer(address(random), false); vm.deal(user1, fee); vm.prank(user1); uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}( @@ -938,12 +939,13 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents { ); } + // TODO: restore this test once providers have custom gas limits, which allow us to toggle between + // the old and new failure behavior. + /* function testRequestWithCallbackAndRevealWithCallbackFailing() public { bytes32 userRandomNumber = bytes32(uint(42)); uint fee = random.getFee(provider1); - EntropyConsumerFails consumer = new EntropyConsumerFails( - address(random) - ); + EntropyConsumer consumer = new EntropyConsumer(address(random), true); vm.deal(address(consumer), fee); vm.startPrank(address(consumer)); uint64 assignedSequenceNumber = random.requestWithCallback{value: fee}( @@ -959,6 +961,155 @@ contract EntropyTest is Test, EntropyTestUtils, EntropyEvents { provider1Proofs[assignedSequenceNumber] ); } + */ + + function testRequestWithRevertingCallback() public { + bytes32 userRandomNumber = bytes32(uint(42)); + uint fee = random.getFee(provider1); + EntropyConsumer consumer = new EntropyConsumer(address(random), true); + vm.deal(user1, fee); + vm.prank(user1); + uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}( + userRandomNumber + ); + + // On the first attempt, the transaction should succeed and emit CallbackFailed event. + bytes memory revertReason = abi.encodeWithSelector( + 0x08c379a0, + "Callback failed" + ); + vm.expectEmit(false, false, false, true, address(random)); + emit CallbackFailed( + provider1, + address(consumer), + assignedSequenceNumber, + userRandomNumber, + provider1Proofs[assignedSequenceNumber], + random.combineRandomValues( + userRandomNumber, + provider1Proofs[assignedSequenceNumber], + 0 + ), + revertReason + ); + random.revealWithCallback( + provider1, + assignedSequenceNumber, + userRandomNumber, + provider1Proofs[assignedSequenceNumber] + ); + + // Verify request is still active after failure + EntropyStructs.Request memory reqAfterFailure = random.getRequest( + provider1, + assignedSequenceNumber + ); + assertEq(reqAfterFailure.sequenceNumber, assignedSequenceNumber); + assertTrue( + reqAfterFailure.callbackStatus == + EntropyStatusConstants.CALLBACK_FAILED + ); + + // On the second attempt, the transaction should directly revert + vm.expectRevert(); + random.revealWithCallback( + provider1, + assignedSequenceNumber, + userRandomNumber, + provider1Proofs[assignedSequenceNumber] + ); + + // Again, request stays active after failure + reqAfterFailure = random.getRequest(provider1, assignedSequenceNumber); + assertEq(reqAfterFailure.sequenceNumber, assignedSequenceNumber); + assertTrue( + reqAfterFailure.callbackStatus == + EntropyStatusConstants.CALLBACK_FAILED + ); + + // If the callback starts succeeding, we can invoke it and it emits the usual RevealedWithCallback event. + consumer.setReverts(false); + vm.expectEmit(false, false, false, true, address(random)); + emit RevealedWithCallback( + reqAfterFailure, + userRandomNumber, + provider1Proofs[assignedSequenceNumber], + random.combineRandomValues( + userRandomNumber, + provider1Proofs[assignedSequenceNumber], + 0 + ) + ); + random.revealWithCallback( + provider1, + assignedSequenceNumber, + userRandomNumber, + provider1Proofs[assignedSequenceNumber] + ); + + // Verify request is cleared after successful reveal + EntropyStructs.Request memory reqAfterReveal = random.getRequest( + provider1, + assignedSequenceNumber + ); + assertEq(reqAfterReveal.sequenceNumber, 0); + } + + function testRequestWithCallbackUsingTooMuchGas() public { + uint64 defaultGasLimit = 100000; + + bytes32 userRandomNumber = bytes32(uint(42)); + uint fee = random.getFee(provider1); + EntropyConsumer consumer = new EntropyConsumer(address(random), false); + // Consumer callback uses ~10% more gas than the provider's default + consumer.setTargetGasUsage((defaultGasLimit * 110) / 100); + + vm.deal(user1, fee); + vm.prank(user1); + uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}( + userRandomNumber + ); + EntropyStructs.Request memory req = random.getRequest( + provider1, + assignedSequenceNumber + ); + + // The transaction reverts if the provider does not provide enough gas to forward + // the gasLimit to the callback transaction. + vm.expectRevert(); + random.revealWithCallback{gas: defaultGasLimit}( + provider1, + assignedSequenceNumber, + userRandomNumber, + provider1Proofs[assignedSequenceNumber] + ); + + // Calling without a gas limit should succeed + vm.expectEmit(false, false, false, true, address(random)); + emit RevealedWithCallback( + req, + userRandomNumber, + provider1Proofs[assignedSequenceNumber], + random.combineRandomValues( + userRandomNumber, + provider1Proofs[assignedSequenceNumber], + 0 + ) + ); + random.revealWithCallback( + provider1, + assignedSequenceNumber, + userRandomNumber, + provider1Proofs[assignedSequenceNumber] + ); + + // Verify request is cleared after successful reveal + EntropyStructs.Request memory reqAfterReveal = random.getRequest( + provider1, + assignedSequenceNumber + ); + assertEq(reqAfterReveal.sequenceNumber, 0); + } function testLastRevealedTooOld() public { for (uint256 i = 0; i < provider1MaxNumHashes; i++) { @@ -1155,9 +1306,14 @@ contract EntropyConsumer is IEntropyConsumer { bytes32 public randomness; address public entropy; address public provider; + bool public reverts; + uint256 public gasUsed; + uint256 public targetGasUsage; - constructor(address _entropy) { + constructor(address _entropy, bool _reverts) { entropy = _entropy; + reverts = _reverts; + targetGasUsage = 0; // Default target } function requestEntropy( @@ -1173,31 +1329,37 @@ contract EntropyConsumer is IEntropyConsumer { return entropy; } + function setReverts(bool _reverts) public { + reverts = _reverts; + } + + function setTargetGasUsage(uint256 _targetGasUsage) public { + targetGasUsage = _targetGasUsage; + } + function entropyCallback( uint64 _sequence, address _provider, bytes32 _randomness ) internal override { - sequence = _sequence; - provider = _provider; - randomness = _randomness; - } -} - -contract EntropyConsumerFails is IEntropyConsumer { - uint64 public sequence; - bytes32 public randomness; - address public entropy; - - constructor(address _entropy) { - entropy = _entropy; - } + uint256 startGas = gasleft(); + uint256 currentGasUsed = 0; + + // Keep consuming gas until we reach our target + while (currentGasUsed < targetGasUsage) { + // Consume gas with a hash operation + keccak256(abi.encodePacked(currentGasUsed, _randomness)); + currentGasUsed = startGas - gasleft(); + } - function getEntropy() internal view override returns (address) { - return entropy; - } + gasUsed = currentGasUsed; - function entropyCallback(uint64, address, bytes32) internal pure override { - revert("Callback failed"); + if (!reverts) { + sequence = _sequence; + provider = _provider; + randomness = _randomness; + } else { + revert("Callback failed"); + } } } diff --git a/target_chains/ethereum/contracts/package.json b/target_chains/ethereum/contracts/package.json index 6f6ef476c4..814972bb7e 100644 --- a/target_chains/ethereum/contracts/package.json +++ b/target_chains/ethereum/contracts/package.json @@ -1,6 +1,6 @@ { "name": "@pythnetwork/pyth-evm-contract", - "version": "1.4.4-alpha.1", + "version": "1.4.4-alpha.2", "description": "", "private": "true", "devDependencies": { @@ -39,6 +39,7 @@ "@openzeppelin/contracts": "=4.8.1", "@openzeppelin/contracts-upgradeable": "=4.8.1", "@openzeppelin/hardhat-upgrades": "^1.22.1", + "@nomad-xyz/excessively-safe-call": "^0.0.1-rc.1", "@pythnetwork/contract-manager": "workspace:*", "@pythnetwork/entropy-sdk-solidity": "workspace:*", "@pythnetwork/pyth-sdk-solidity": "workspace:*", diff --git a/target_chains/ethereum/contracts/remappings.txt b/target_chains/ethereum/contracts/remappings.txt index 4b2c7905c0..88dbecf8b1 100644 --- a/target_chains/ethereum/contracts/remappings.txt +++ b/target_chains/ethereum/contracts/remappings.txt @@ -3,4 +3,5 @@ @pythnetwork/=./node_modules/@pythnetwork/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ +@nomad-xyz=./node_modules/@nomad-xyz/ truffle/=./node_modules/truffle/ diff --git a/target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol b/target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol index 59b595fb13..6587cefe41 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/EntropyEvents.sol @@ -29,6 +29,16 @@ interface EntropyEvents { bytes32 randomNumber ); + event CallbackFailed( + address indexed provider, + address indexed requestor, + uint64 indexed sequenceNumber, + bytes32 userRandomNumber, + bytes32 providerRevelation, + bytes32 randomNumber, + bytes errorCode + ); + event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee); event ProviderUriUpdated(address provider, bytes oldUri, bytes newUri); diff --git a/target_chains/ethereum/entropy_sdk/solidity/EntropyStatusConstants.sol b/target_chains/ethereum/entropy_sdk/solidity/EntropyStatusConstants.sol new file mode 100644 index 0000000000..cace177264 --- /dev/null +++ b/target_chains/ethereum/entropy_sdk/solidity/EntropyStatusConstants.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache 2 + +library EntropyStatusConstants { + // Status values for Request.status // + // not a request with callback + uint8 public constant CALLBACK_NOT_NECESSARY = 0; + // A request with callback where the callback hasn't been invoked yet. + uint8 public constant CALLBACK_NOT_STARTED = 1; + // A request with callback where the callback is currently in flight (this state is a reentry guard). + uint8 public constant CALLBACK_IN_PROGRESS = 2; + // A request with callback where the callback has been invoked and failed. + uint8 public constant CALLBACK_FAILED = 3; +} diff --git a/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol b/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol index 15c1d1511a..c0092ff1c6 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/EntropyStructs.sol @@ -59,8 +59,8 @@ contract EntropyStructs { address requester; // If true, incorporate the blockhash of blockNumber into the generated random value. bool useBlockhash; - // If true, the requester will be called back with the generated random value. - bool isRequestWithCallback; - // There are 2 remaining bytes of free space in this slot. + // Status flag for requests with callbacks. See EntropyConstants for the possible values of this flag. + uint8 callbackStatus; + // 2 bytes of space left in this struct. } } diff --git a/target_chains/ethereum/entropy_sdk/solidity/abis/EntropyEvents.json b/target_chains/ethereum/entropy_sdk/solidity/abis/EntropyEvents.json index b34dffcf59..164c3d4b19 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/abis/EntropyEvents.json +++ b/target_chains/ethereum/entropy_sdk/solidity/abis/EntropyEvents.json @@ -1,4 +1,53 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "requestor", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint64", + "name": "sequenceNumber", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "userRandomNumber", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "providerRevelation", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "randomNumber", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "errorCode", + "type": "bytes" + } + ], + "name": "CallbackFailed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -215,9 +264,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -294,9 +343,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -349,9 +398,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -428,9 +477,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, diff --git a/target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json b/target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json index 61a4a6be2e..1c6de9d2d3 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json +++ b/target_chains/ethereum/entropy_sdk/solidity/abis/IEntropy.json @@ -1,4 +1,53 @@ [ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "provider", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "requestor", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint64", + "name": "sequenceNumber", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "userRandomNumber", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "providerRevelation", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "randomNumber", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "errorCode", + "type": "bytes" + } + ], + "name": "CallbackFailed", + "type": "event" + }, { "anonymous": false, "inputs": [ @@ -215,9 +264,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -294,9 +343,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -349,9 +398,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -428,9 +477,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "indexed": false, @@ -735,9 +784,9 @@ "type": "bool" }, { - "internalType": "bool", - "name": "isRequestWithCallback", - "type": "bool" + "internalType": "uint8", + "name": "callbackStatus", + "type": "uint8" } ], "internalType": "struct EntropyStructs.Request",