diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 5e917702..a88f0c8e 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -56,8 +56,47 @@ contract Oracle is IOracle { } } - // TODO: [OPO-86] Same as `createRequest` but with multiple requests passed in as an array - function createRequests(bytes[] calldata _requestsData) external returns (bytes32[] memory _requestsIds) {} + function createRequests(NewRequest[] calldata _requestsData) external returns (bytes32[] memory _batchRequestsIds) { + uint256 _requestsAmount = _requestsData.length; + _batchRequestsIds = new bytes32[](_requestsAmount); + + for (uint256 _i = 0; _i < _requestsAmount;) { + uint256 _requestNonce = _nonce++; + bytes32 _requestId = keccak256(abi.encodePacked(msg.sender, address(this), _requestNonce)); + + _batchRequestsIds[_i] = _requestId; + _requestIds[_requestNonce] = _requestId; + + Request memory _storedRequest = Request({ + ipfsHash: _requestsData[_i].ipfsHash, + requestModule: _requestsData[_i].requestModule, + responseModule: _requestsData[_i].responseModule, + disputeModule: _requestsData[_i].disputeModule, + resolutionModule: _requestsData[_i].resolutionModule, + finalityModule: _requestsData[_i].finalityModule, + requester: msg.sender, + nonce: _requestNonce, + createdAt: block.timestamp + }); + + _requests[_requestId] = _storedRequest; + + _requestsData[_i].requestModule.setupRequest(_requestId, _requestsData[_i].requestModuleData); + _requestsData[_i].responseModule.setupRequest(_requestId, _requestsData[_i].responseModuleData); + _requestsData[_i].disputeModule.setupRequest(_requestId, _requestsData[_i].disputeModuleData); + + if (address(_requestsData[_i].resolutionModule) != address(0)) { + _requestsData[_i].resolutionModule.setupRequest(_requestId, _requestsData[_i].resolutionModuleData); + } + + if (address(_requestsData[_i].finalityModule) != address(0)) { + _requestsData[_i].finalityModule.setupRequest(_requestId, _requestsData[_i].finalityModuleData); + } + unchecked { + ++_i; + } + } + } function listRequests(uint256 _startFrom, uint256 _batchSize) external view returns (FullRequest[] memory _list) { uint256 _totalRequestsCount = _nonce; diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 1b074935..e2b0c0cb 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -104,9 +104,11 @@ interface IOracle { /** * @notice Creates multiple requests, the same way as createRequest * @param _requestsData The array of calldata for each request - * @return _requestIds The array of request IDs + * @return _batchRequestsIds The array of request IDs */ - function createRequests(bytes[] calldata _requestsData) external returns (bytes32[] memory _requestIds); + function createRequests(IOracle.NewRequest[] calldata _requestsData) + external + returns (bytes32[] memory _batchRequestsIds); function validModule(bytes32 _requestId, address _module) external view returns (bool _validModule); function getDispute(bytes32 _disputeId) external view returns (Dispute memory _dispute); diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index fbafcd4b..6e2f976f 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -202,6 +202,127 @@ contract Oracle_UnitTest is Test { assertEq(_storedRequest.createdAt, block.timestamp); // should be set } + /** + * @notice Test creation of requests in batch mode. + */ + function test_createRequests( + bytes calldata _requestData, + bytes calldata _responseData, + bytes calldata _disputeData + ) public { + uint256 _initialNonce = uint256(vm.load(address(oracle), bytes32(uint256(0x7)))); + + uint256 _requestsAmount = 5; + + IOracle.NewRequest[] memory _requests = new IOracle.NewRequest[](_requestsAmount); + + bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); + + bool _useResolutionAndFinality = _requestData.length % 2 == 0; + + // Generate requests batch + for (uint256 _i = 0; _i < _requestsAmount; _i++) { + if (!_useResolutionAndFinality) { + disputeModule = IDisputeModule(address(0)); + finalityModule = IFinalityModule(address(0)); + } + + IOracle.NewRequest memory _request = IOracle.NewRequest({ + requestModuleData: _requestData, + responseModuleData: _responseData, + disputeModuleData: _disputeData, + resolutionModuleData: bytes(''), + finalityModuleData: bytes(''), + ipfsHash: bytes32('69'), + requestModule: requestModule, + responseModule: responseModule, + disputeModule: disputeModule, + resolutionModule: resolutionModule, + finalityModule: finalityModule + }); + + bytes32 _theoricRequestId = keccak256(abi.encodePacked(sender, address(oracle), _initialNonce + _i)); + _requests[_i] = _request; + _precalculatedIds[_i] = _theoricRequestId; + + if (_useResolutionAndFinality) { + vm.mockCall( + address(disputeModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.resolutionModuleData)), + abi.encode() + ); + vm.expectCall( + address(resolutionModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.resolutionModuleData)) + ); + + vm.mockCall( + address(finalityModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.finalityModuleData)), + abi.encode() + ); + vm.expectCall( + address(finalityModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.finalityModuleData)) + ); + } + + // mock and expect disputeModule call + vm.mockCall( + address(disputeModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.disputeModuleData)), + abi.encode() + ); + vm.expectCall( + address(disputeModule), abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.disputeModuleData)) + ); + + // mock and expect requestModule and responseModule calls + vm.mockCall( + address(requestModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.requestModuleData)), + abi.encode() + ); + vm.expectCall( + address(requestModule), abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.requestModuleData)) + ); + + vm.mockCall( + address(responseModule), + abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.responseModuleData)), + abi.encode() + ); + vm.expectCall( + address(responseModule), abi.encodeCall(IModule.setupRequest, (_theoricRequestId, _request.responseModuleData)) + ); + } + + vm.prank(sender); + bytes32[] memory _requestsIds = oracle.createRequests(_requests); + + for (uint256 _i = 0; _i < _requestsIds.length; _i++) { + assertEq(_requestsIds[_i], _precalculatedIds[_i]); + + IOracle.Request memory _storedRequest = oracle.getRequest(_requestsIds[_i]); + + // Check: request values correctly stored - unchanged ones + assertEq(_storedRequest.ipfsHash, _requests[_i].ipfsHash); + assertEq(address(_storedRequest.requestModule), address(_requests[_i].requestModule)); + assertEq(address(_storedRequest.disputeModule), address(_requests[_i].disputeModule)); + assertEq(address(_storedRequest.resolutionModule), address(_requests[_i].resolutionModule)); + assertEq(address(_storedRequest.finalityModule), address(_requests[_i].finalityModule)); + + // Check: request values correctly stored - ones set by the oracle + assertEq(_storedRequest.requester, sender); // should be set + assertEq(_storedRequest.nonce, _initialNonce + _i); + assertEq(_storedRequest.createdAt, block.timestamp); // should be set + } + + // Read the slot 7 (internal var) which holds the nonce + uint256 _newNonce = uint256(vm.load(address(oracle), bytes32(uint256(0x7)))); + assertEq(_newNonce, _initialNonce + _requestsAmount); + } + /** * @notice Test list requests, fuzz start and batch size */