Skip to content

Commit

Permalink
feat: batch request creation (#57)
Browse files Browse the repository at this point in the history
# 🤖 Linear

Closes OPO-86

This PR introduces the implementation of `createRequests` which allows
users to create multiple requests from a single call. Unit test
included.
  • Loading branch information
0xmoebius authored Jul 27, 2023
2 parents 5fda5ad + 3917493 commit 2faed3b
Show file tree
Hide file tree
Showing 3 changed files with 166 additions and 4 deletions.
43 changes: 41 additions & 2 deletions solidity/contracts/Oracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions solidity/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
121 changes: 121 additions & 0 deletions solidity/test/unit/Oracle.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down

0 comments on commit 2faed3b

Please sign in to comment.