diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 158d52a7..61ad8cce 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -91,25 +91,29 @@ contract Oracle is IOracle { _dispute = _disputes[_disputeId]; } - function getProposers(bytes32 _requestId) external view returns (address[] memory _proposers) { - bytes32[] memory _responsesIds = _responseIds[_requestId]; - if (_responsesIds.length == 0) return _proposers; - _proposers = new address[](_responsesIds.length); - - for (uint256 _i; _i < _responsesIds.length;) { - _proposers[_i] = _responses[_responsesIds[_i]].proposer; - - unchecked { - ++_i; - } - } - } - function proposeResponse(bytes32 _requestId, bytes calldata _responseData) external returns (bytes32 _responseId) { Request memory _request = _requests[_requestId]; + _responseId = _proposeResponse(msg.sender, _requestId, _request, _responseData); + } - _responseId = keccak256(abi.encodePacked(msg.sender, address(this), _requestId, _responseNonce++)); - _responses[_responseId] = _request.responseModule.propose(_requestId, msg.sender, _responseData); + function proposeResponse( + address _proposer, + bytes32 _requestId, + bytes calldata _responseData + ) external returns (bytes32 _responseId) { + Request memory _request = _requests[_requestId]; + if (msg.sender != address(_request.disputeModule)) revert Oracle_NotDisputeModule(msg.sender); + _responseId = _proposeResponse(_proposer, _requestId, _request, _responseData); + } + + function _proposeResponse( + address _proposer, + bytes32 _requestId, + Request memory _request, + bytes calldata _responseData + ) internal returns (bytes32 _responseId) { + _responseId = keccak256(abi.encodePacked(_proposer, address(this), _requestId, _responseNonce++)); + _responses[_responseId] = _request.responseModule.propose(_requestId, _proposer, _responseData); _responseIds[_requestId].push(_responseId); } diff --git a/solidity/contracts/modules/BondEscalationModule.sol b/solidity/contracts/modules/BondEscalationModule.sol index d973758c..bef53300 100644 --- a/solidity/contracts/modules/BondEscalationModule.sol +++ b/solidity/contracts/modules/BondEscalationModule.sol @@ -109,7 +109,6 @@ contract BondEscalationModule is Module, IBondEscalationModule { escalatedDispute[_requestId] = _disputeId; } - // TODO: Check if can dispute _dispute = IOracle.Dispute({ disputer: _disputer, responseId: _responseId, diff --git a/solidity/contracts/modules/BondedDisputeModule.sol b/solidity/contracts/modules/BondedDisputeModule.sol index 3edb7c0c..c0420bd2 100644 --- a/solidity/contracts/modules/BondedDisputeModule.sol +++ b/solidity/contracts/modules/BondedDisputeModule.sol @@ -32,7 +32,6 @@ contract BondedDisputeModule is Module, IBondedDisputeModule { address _disputer, address _proposer ) external onlyOracle returns (IOracle.Dispute memory _dispute) { - // TODO: Check if can dispute _dispute = IOracle.Dispute({ disputer: _disputer, responseId: _responseId, diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 300eaf2c..4a8aa7e8 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -10,6 +10,7 @@ import {IFinalityModule} from './modules/IFinalityModule.sol'; interface IOracle { /// @notice Thrown when the caller of the slash() function is not the DisputeModule error Oracle_NotResolutionModule(address _caller); + error Oracle_NotDisputeModule(address _caller); error Oracle_ResponseAlreadyDisputed(bytes32 _responseId); error Oracle_AlreadyFinalized(bytes32 _requestId); @@ -83,13 +84,17 @@ interface IOracle { function getRequest(bytes32 _requestId) external view returns (Request memory _request); function disputeOf(bytes32 _requestId) external view returns (bytes32 _disputeId); function proposeResponse(bytes32 _requestId, bytes calldata _responseData) external returns (bytes32 _responseId); + function proposeResponse( + address _proposer, + bytes32 _requestId, + bytes calldata _responseData + ) external returns (bytes32 _responseId); function disputeResponse(bytes32 _requestId, bytes32 _responseId) external returns (bytes32 _disputeId); function escalateDispute(bytes32 _disputeId) external; function getFinalizedResponse(bytes32 _requestId) external view returns (Response memory _response); function getResponseIds(bytes32 _requestId) external view returns (bytes32[] memory _ids); function resolveDispute(bytes32 _disputeId) external; function updateDisputeStatus(bytes32 _disputeId, DisputeStatus _status) external; - function getProposers(bytes32 _requestId) external view returns (address[] memory _proposers); function listRequests(uint256 _startFrom, uint256 _amount) external view returns (Request[] memory _list); function listRequestIds(uint256 _startFrom, uint256 _batchSize) external view returns (bytes32[] memory _list); function finalize(bytes32 _requestId, bytes32 _finalizedResponseId) external; diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 65d683d5..72fe8158 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -329,6 +329,75 @@ contract Oracle_UnitTest is Test { assertEq(_responseIds[1], _secondResponseId); } + /** + * @notice Test dispute module proposes a response as somebody else: check _responses, _responseIds and _responseId + */ + function test_proposeResponseWithProposer(address _proposer, bytes calldata _responseData) public { + vm.assume(_proposer != address(0)); + + // Create mock request and store it + bytes32 _requestId = _storeDummyRequests(1)[0]; + + // Get the current response nonce (8th slot) + uint256 _responseNonce = uint256(vm.load(address(oracle), bytes32(uint256(0x8)))); + + // Compute the response ID + bytes32 _responseId = keccak256(abi.encodePacked(_proposer, address(oracle), _requestId, _responseNonce)); + + // Create mock response + IOracle.Response memory _response = IOracle.Response({ + createdAt: block.timestamp, + proposer: _proposer, + requestId: _requestId, + disputeId: bytes32('69'), + response: _responseData + }); + + // Test: revert if called by a random dude (not dispute module) + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NotDisputeModule.selector, sender)); + vm.prank(sender); + oracle.proposeResponse(_proposer, _requestId, _responseData); + + // Mock&expect the responseModule propose call: + vm.mockCall( + address(responseModule), + abi.encodeCall(IResponseModule.propose, (_requestId, _proposer, _responseData)), + abi.encode(_response) + ); + vm.expectCall( + address(responseModule), abi.encodeCall(IResponseModule.propose, (_requestId, _proposer, _responseData)) + ); + + // Test: propose the response + vm.prank(address(disputeModule)); + bytes32 _actualResponseId = oracle.proposeResponse(_proposer, _requestId, _responseData); + + vm.prank(address(disputeModule)); + bytes32 _secondResponseId = oracle.proposeResponse(_proposer, _requestId, _responseData); + + // Check: correct response id returned? + assertEq(_actualResponseId, _responseId); + + // Check: responseId are unique? + assertNotEq(_secondResponseId, _responseId); + + IOracle.Response memory _storedResponse = oracle.getResponse(_responseId); + + // Check: correct response stored? + assertEq(_storedResponse.createdAt, _response.createdAt); + assertEq(_storedResponse.proposer, _response.proposer); + assertEq(_storedResponse.requestId, _response.requestId); + assertEq(_storedResponse.disputeId, _response.disputeId); + assertEq(_storedResponse.response, _response.response); + + bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); + + // Check: correct response id stored in the id list and unique? + assertEq(_responseIds.length, 2); + assertEq(_responseIds[0], _responseId); + assertEq(_responseIds[1], _secondResponseId); + } + /** * @notice Test dispute response: check _responses, _responseIds and _responseId */