diff --git a/solidity/contracts/Module.sol b/solidity/contracts/Module.sol index c47af2ae..13e1696e 100644 --- a/solidity/contracts/Module.sol +++ b/solidity/contracts/Module.sol @@ -3,14 +3,10 @@ pragma solidity ^0.8.19; import {IModule} from '../interfaces/IModule.sol'; import {IOracle} from '../interfaces/IOracle.sol'; +import {Validator} from './Validator.sol'; -abstract contract Module is IModule { - /// @inheritdoc IModule - IOracle public immutable ORACLE; - - constructor(IOracle _oracle) payable { - ORACLE = _oracle; - } +abstract contract Module is Validator, IModule { + constructor(IOracle _oracle) payable Validator(_oracle) {} /** * @notice Checks that the caller is the oracle @@ -29,107 +25,4 @@ abstract contract Module is IModule { /// @inheritdoc IModule function validateParameters(bytes calldata _encodedParameters) external view virtual returns (bool _valid) {} - - /** - * @notice Computes the id a given request - * - * @param _request The request to compute the id for - * @return _id The id the request - */ - function _getId(IOracle.Request calldata _request) internal pure returns (bytes32 _id) { - _id = keccak256(abi.encode(_request)); - } - - /** - * @notice Computes the id a given response - * - * @param _response The response to compute the id for - * @return _id The id the response - */ - function _getId(IOracle.Response calldata _response) internal pure returns (bytes32 _id) { - _id = keccak256(abi.encode(_response)); - } - - /** - * @notice Computes the id a given dispute - * - * @param _dispute The dispute to compute the id for - * @return _id The id the dispute - */ - function _getId(IOracle.Dispute calldata _dispute) internal pure returns (bytes32 _id) { - _id = keccak256(abi.encode(_dispute)); - } - - /** - * @notice Validates the correctness of a request-response pair - * - * @param _request The request to compute the id for - * @param _response The response to compute the id for - * @return _responseId The id the response - */ - function _validateResponse( - IOracle.Request calldata _request, - IOracle.Response calldata _response - ) internal pure returns (bytes32 _responseId) { - bytes32 _requestId = _getId(_request); - _responseId = _getId(_response); - - if (_response.requestId != _requestId) revert Module_InvalidResponseBody(); - } - - /** - * @notice Validates the correctness of a request-dispute pair - * - * @param _request The request to compute the id for - * @param _dispute The dispute to compute the id for - * @return _disputeId The id the dispute - */ - function _validateDispute( - IOracle.Request calldata _request, - IOracle.Dispute calldata _dispute - ) internal pure returns (bytes32 _disputeId) { - bytes32 _requestId = _getId(_request); - _disputeId = _getId(_dispute); - - if (_dispute.requestId != _requestId) revert Module_InvalidDisputeBody(); - } - - /** - * @notice Validates the correctness of a response-dispute pair - * - * @param _response The response to compute the id for - * @param _dispute The dispute to compute the id for - * @return _disputeId The id the dispute - */ - function _validateDispute( - IOracle.Response calldata _response, - IOracle.Dispute calldata _dispute - ) internal pure returns (bytes32 _disputeId) { - bytes32 _responseId = _getId(_response); - _disputeId = _getId(_dispute); - - if (_dispute.responseId != _responseId) revert Module_InvalidDisputeBody(); - } - - /** - * @notice Validates the correctness of a request-response-dispute triplet - * - * @param _request The request to compute the id for - * @param _response The response to compute the id for - * @param _dispute The dispute to compute the id for - * @return _responseId The id the response - * @return _disputeId The id the dispute - */ - function _validateResponseAndDispute( - IOracle.Request calldata _request, - IOracle.Response calldata _response, - IOracle.Dispute calldata _dispute - ) internal pure returns (bytes32 _responseId, bytes32 _disputeId) { - bytes32 _requestId = _getId(_request); - _responseId = _getId(_response); - _disputeId = _getId(_dispute); - - if (_response.requestId != _requestId) revert Module_InvalidResponseBody(); - if (_dispute.requestId != _requestId || _dispute.responseId != _responseId) revert Module_InvalidDisputeBody(); - } } diff --git a/solidity/contracts/Validator.sol b/solidity/contracts/Validator.sol new file mode 100644 index 00000000..d2694d12 --- /dev/null +++ b/solidity/contracts/Validator.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IOracle} from '../interfaces/IOracle.sol'; +import {IValidator} from '../interfaces/IValidator.sol'; + +contract Validator is IValidator { + /// @inheritdoc IValidator + IOracle public immutable ORACLE; + + constructor(IOracle _oracle) payable { + ORACLE = _oracle; + } + + /** + * @notice Computes the id a given request + * + * @param _request The request to compute the id for + * @return _id The id the request + */ + function _getId(IOracle.Request calldata _request) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_request)); + } + + /** + * @notice Computes the id a given response + * + * @param _response The response to compute the id for + * @return _id The id the response + */ + function _getId(IOracle.Response calldata _response) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_response)); + } + + /** + * @notice Computes the id a given dispute + * + * @param _dispute The dispute to compute the id for + * @return _id The id the dispute + */ + function _getId(IOracle.Dispute calldata _dispute) internal pure returns (bytes32 _id) { + _id = keccak256(abi.encode(_dispute)); + } + + /** + * @notice Validates the correctness and existance of a request-response pair + * + * @param _request The request to compute the id for + * @param _response The response to compute the id for + * @return _responseId The id the response + */ + function _validateResponse( + IOracle.Request calldata _request, + IOracle.Response calldata _response + ) internal view returns (bytes32 _responseId) { + bytes32 _requestId = _getId(_request); + _responseId = _getId(_response); + + if (_response.requestId != _requestId) revert Validator_InvalidResponseBody(); + if (ORACLE.responseCreatedAt(_responseId) == 0) revert Validator_InvalidResponse(); + } + + /** + * @notice Validates the correctness of a request-dispute pair + * + * @param _request The request to compute the id for + * @param _dispute The dispute to compute the id for + * @return _disputeId The id the dispute + */ + function _validateDispute( + IOracle.Request calldata _request, + IOracle.Dispute calldata _dispute + ) internal view returns (bytes32 _disputeId) { + bytes32 _requestId = _getId(_request); + _disputeId = _getId(_dispute); + + if (_dispute.requestId != _requestId) revert Validator_InvalidDisputeBody(); + if (ORACLE.disputeCreatedAt(_disputeId) == 0) revert Validator_InvalidDispute(); + } + + /** + * @notice Validates the correctness of a response-dispute pair + * + * @param _response The response to compute the id for + * @param _dispute The dispute to compute the id for + * @return _disputeId The id the dispute + */ + function _validateDispute( + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) internal view returns (bytes32 _disputeId) { + bytes32 _responseId = _getId(_response); + _disputeId = _getId(_dispute); + + if (_dispute.responseId != _responseId) revert Validator_InvalidDisputeBody(); + if (ORACLE.disputeCreatedAt(_disputeId) == 0) revert Validator_InvalidDispute(); + } + + /** + * @notice Validates the correctness of a request-response-dispute triplet + * + * @param _request The request to compute the id for + * @param _response The response to compute the id for + * @param _dispute The dispute to compute the id for + * @return _responseId The id the response + * @return _disputeId The id the dispute + */ + function _validateResponseAndDispute( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) internal view returns (bytes32 _responseId, bytes32 _disputeId) { + bytes32 _requestId = _getId(_request); + _responseId = _getId(_response); + _disputeId = _getId(_dispute); + + if (_response.requestId != _requestId) revert Validator_InvalidResponseBody(); + if (_dispute.requestId != _requestId || _dispute.responseId != _responseId) revert Validator_InvalidDisputeBody(); + if (ORACLE.disputeCreatedAt(_disputeId) == 0) revert Validator_InvalidDispute(); + } +} diff --git a/solidity/interfaces/IModule.sol b/solidity/interfaces/IModule.sol index 25a050a3..e03af529 100644 --- a/solidity/interfaces/IModule.sol +++ b/solidity/interfaces/IModule.sol @@ -29,27 +29,6 @@ interface IModule { */ error Module_OnlyOracle(); - /** - * @notice Thrown when the response provided does not match the request - */ - error Module_InvalidResponseBody(); - - /** - * @notice Thrown when the dispute provided does not match the request or response - */ - error Module_InvalidDisputeBody(); - - /*/////////////////////////////////////////////////////////////// - VARIABLES - //////////////////////////////////////////////////////////////*/ - - /** - * @notice Returns the address of the oracle - * - * @return _oracle The address of the oracle - */ - function ORACLE() external view returns (IOracle _oracle); - /*/////////////////////////////////////////////////////////////// LOGIC //////////////////////////////////////////////////////////////*/ diff --git a/solidity/interfaces/IValidator.sol b/solidity/interfaces/IValidator.sol new file mode 100644 index 00000000..3b1e1981 --- /dev/null +++ b/solidity/interfaces/IValidator.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IOracle} from './IOracle.sol'; + +/** + * @title Validator + * @notice Contract to validate requests, responses, and disputes + */ +interface IValidator { + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Thrown when the response provided does not match the request + */ + error Validator_InvalidResponseBody(); + + /** + * @notice Thrown when the dispute provided does not match the request or response + */ + error Validator_InvalidDisputeBody(); + + /** + * @notice Thrown when the response provided does not exist + */ + error Validator_InvalidResponse(); + + /** + * @notice Thrown when the dispute provided does not exist + */ + error Validator_InvalidDispute(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Returns the address of the oracle + * + * @return _oracle The address of the oracle + */ + function ORACLE() external view returns (IOracle _oracle); +} diff --git a/solidity/test/unit/Validator.t.sol b/solidity/test/unit/Validator.t.sol new file mode 100644 index 00000000..182dbb55 --- /dev/null +++ b/solidity/test/unit/Validator.t.sol @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {IOracle} from '../../interfaces/IOracle.sol'; + +import {IDisputeModule} from '../../interfaces/modules/dispute/IDisputeModule.sol'; + +import {IFinalityModule} from '../../interfaces/modules/finality/IFinalityModule.sol'; +import {IRequestModule} from '../../interfaces/modules/request/IRequestModule.sol'; +import {IResolutionModule} from '../../interfaces/modules/resolution/IResolutionModule.sol'; +import {IResponseModule} from '../../interfaces/modules/response/IResponseModule.sol'; + +import {Validator} from '../../contracts/Validator.sol'; +import {IValidator} from '../../interfaces/IValidator.sol'; +import {Helpers} from '../utils/Helpers.sol'; + +/** + * @notice Harness to deploy and test Oracle + */ +contract MockValidator is Validator { + constructor(IOracle _oracle) Validator(_oracle) {} + + function getId(IOracle.Request calldata _request) external pure returns (bytes32 _requestId) { + return _getId(_request); + } + + function getId(IOracle.Response calldata _response) external pure returns (bytes32 _responseId) { + return _getId(_response); + } + + function getId(IOracle.Dispute calldata _dispute) external pure returns (bytes32 _disputeId) { + return _getId(_dispute); + } + + function validateResponse( + IOracle.Request calldata _request, + IOracle.Response calldata _response + ) external view returns (bytes32 _responseId) { + return _validateResponse(_request, _response); + } + + function validateDispute( + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external view returns (bytes32 _disputeId) { + return _validateDispute(_response, _dispute); + } + + function validateDispute( + IOracle.Request calldata _request, + IOracle.Dispute calldata _dispute + ) external view returns (bytes32 _disputeId) { + return _validateDispute(_request, _dispute); + } + + function validateResponseAndDispute( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external view returns (bytes32 _responseId, bytes32 _disputeId) { + return _validateResponseAndDispute(_request, _response, _dispute); + } +} + +/** + * @title Oracle Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + MockValidator public validator; + + // Mock Oracle + IOracle public oracle = IOracle(_mockContract('oracle')); + + // Mock modules + IRequestModule public requestModule = IRequestModule(_mockContract('requestModule')); + IResponseModule public responseModule = IResponseModule(_mockContract('responseModule')); + IDisputeModule public disputeModule = IDisputeModule(_mockContract('disputeModule')); + IResolutionModule public resolutionModule = IResolutionModule(_mockContract('resolutionModule')); + IFinalityModule public finalityModule = IFinalityModule(_mockContract('finalityModule')); + + function setUp() public virtual { + validator = new MockValidator(oracle); + + mockRequest.requestModule = address(requestModule); + mockRequest.responseModule = address(responseModule); + mockRequest.disputeModule = address(disputeModule); + mockRequest.resolutionModule = address(resolutionModule); + mockRequest.finalityModule = address(finalityModule); + + mockResponse.requestId = _getId(mockRequest); + mockDispute.requestId = mockResponse.requestId; + mockDispute.responseId = _getId(mockResponse); + + vm.mockCall( + address(oracle), + abi.encodeWithSelector(IOracle.responseCreatedAt.selector, _getId(mockResponse)), + abi.encode(1000) + ); + vm.mockCall( + address(oracle), abi.encodeWithSelector(IOracle.disputeCreatedAt.selector, _getId(mockDispute)), abi.encode(1000) + ); + } +} + +contract ValidatorGetIds is BaseTest { + function test_getId_request() public { + bytes32 _requestId = validator.getId(mockRequest); + assertEq(_requestId, keccak256(abi.encode(mockRequest))); + } + + function test_getId_response() public { + bytes32 _responseId = validator.getId(mockResponse); + assertEq(_responseId, keccak256(abi.encode(mockResponse))); + } + + function test_getId_dispute() public { + bytes32 _disputeId = validator.getId(mockDispute); + assertEq(_disputeId, keccak256(abi.encode(mockDispute))); + } +} + +contract ValidatorValidateResponse is BaseTest { + function test_validateResponse() public { + bytes32 _responseId = validator.validateResponse(mockRequest, mockResponse); + assertEq(_responseId, keccak256(abi.encode(mockResponse))); + } + + function test_validateResponse_InvalidResponseBody() public { + IOracle.Response memory response = mockResponse; + response.requestId = bytes32('invalid'); + vm.expectRevert(IValidator.Validator_InvalidResponseBody.selector); + validator.validateResponse(mockRequest, response); + } + + function test_validateResponse_InvalidResponse() public { + vm.mockCall( + address(oracle), abi.encodeWithSelector(IOracle.responseCreatedAt.selector, _getId(mockResponse)), abi.encode(0) + ); + vm.expectRevert(IValidator.Validator_InvalidResponse.selector); + validator.validateResponse(mockRequest, mockResponse); + } +} + +contract ValidatorValidateDisputeRequest is BaseTest { + function test_validateDispute() public { + bytes32 _disputeId = validator.validateDispute(mockRequest, mockDispute); + assertEq(_disputeId, keccak256(abi.encode(mockDispute))); + } + + function test_validateDispute_InvalidDisputeBody() public { + IOracle.Dispute memory dispute = mockDispute; + dispute.requestId = bytes32('invalid'); + vm.expectRevert(IValidator.Validator_InvalidDisputeBody.selector); + validator.validateDispute(mockRequest, dispute); + } + + function test_validateDispute_InvalidDispute() public { + vm.mockCall( + address(oracle), abi.encodeWithSelector(IOracle.disputeCreatedAt.selector, _getId(mockDispute)), abi.encode(0) + ); + vm.expectRevert(IValidator.Validator_InvalidDispute.selector); + validator.validateDispute(mockRequest, mockDispute); + } +} + +contract ValidatorValidateDisputeResponse is BaseTest { + function test_validateDispute() public { + bytes32 _disputeId = validator.validateDispute(mockResponse, mockDispute); + assertEq(_disputeId, keccak256(abi.encode(mockDispute))); + } + + function test_validateDispute_InvalidDisputeBody() public { + IOracle.Dispute memory dispute = mockDispute; + dispute.responseId = bytes32('invalid'); + vm.expectRevert(IValidator.Validator_InvalidDisputeBody.selector); + validator.validateDispute(mockResponse, dispute); + } + + function test_validateDispute_InvalidDispute() public { + vm.mockCall( + address(oracle), abi.encodeWithSelector(IOracle.disputeCreatedAt.selector, _getId(mockDispute)), abi.encode(0) + ); + vm.expectRevert(IValidator.Validator_InvalidDispute.selector); + validator.validateDispute(mockResponse, mockDispute); + } +} + +contract ValidatorValidateResponseAndDispute is BaseTest { + function test_validateResponseAndDispute() public { + (bytes32 responseId, bytes32 disputeId) = + validator.validateResponseAndDispute(mockRequest, mockResponse, mockDispute); + assertEq(responseId, keccak256(abi.encode(mockResponse))); + assertEq(disputeId, keccak256(abi.encode(mockDispute))); + } + + function test_validateResponseAndDispute_InvalidResponseBody() public { + IOracle.Response memory response = mockResponse; + response.requestId = bytes32('invalid'); + vm.expectRevert(IValidator.Validator_InvalidResponseBody.selector); + validator.validateResponseAndDispute(mockRequest, response, mockDispute); + } + + function test_validateResponseAndDispute_InvalidDisputeBody() public { + IOracle.Dispute memory dispute = mockDispute; + dispute.requestId = bytes32('invalid'); + vm.expectRevert(IValidator.Validator_InvalidDisputeBody.selector); + validator.validateResponseAndDispute(mockRequest, mockResponse, dispute); + } + + function test_validateResponseAndDispute_InvalidDisputeBodyResponseId() public { + IOracle.Dispute memory dispute = mockDispute; + dispute.responseId = bytes32('invalid'); + vm.expectRevert(IValidator.Validator_InvalidDisputeBody.selector); + validator.validateResponseAndDispute(mockRequest, mockResponse, dispute); + } + + function test_validateResponseAndDispute_InvalidDispute() public { + vm.mockCall( + address(oracle), abi.encodeWithSelector(IOracle.disputeCreatedAt.selector, _getId(mockDispute)), abi.encode(0) + ); + vm.expectRevert(IValidator.Validator_InvalidDispute.selector); + validator.validateResponseAndDispute(mockRequest, mockResponse, mockDispute); + } +}