From b3222f23790e0d1af52acbcec29d0922b8605223 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Thu, 17 Oct 2024 15:23:55 -0300 Subject: [PATCH 01/19] feat: access control --- solidity/contracts/AccessController.sol | 33 + solidity/contracts/Oracle.sol | 174 +- solidity/contracts/utils/OracleTypehash.sol | 17 + solidity/interfaces/IAccessController.sol | 29 + solidity/interfaces/IOracle.sol | 174 +- .../accessControl/IAccessControlModule.sol | 27 + .../test/integration/EscalateDispute.t.sol | 44 +- solidity/test/integration/Finalization.t.sol | 272 +- .../test/integration/ResponseDispute.t.sol | 66 +- .../test/integration/ResponseProposal.t.sol | 60 +- .../mocks/contracts/MockAtomicArbitrator.sol | 2 +- solidity/test/unit/Oracle.t.sol | 2522 ++++++++--------- 12 files changed, 1810 insertions(+), 1610 deletions(-) create mode 100644 solidity/contracts/AccessController.sol create mode 100644 solidity/contracts/utils/OracleTypehash.sol create mode 100644 solidity/interfaces/IAccessController.sol create mode 100644 solidity/interfaces/modules/accessControl/IAccessControlModule.sol diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol new file mode 100644 index 0000000..344e513 --- /dev/null +++ b/solidity/contracts/AccessController.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IAccessController} from '../interfaces/IAccessController.sol'; +import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.sol'; + +abstract contract AccessController is IAccessController { + /** + * @notice Modifier to check if the caller has access to the user + * @param _accessControlModule The access control module + * @param _accessControl The access control struct + */ + modifier hasAccess( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + AccessControl memory _accessControl + ) { + bool _hasAccess = msg.sender == _accessControl.user + || ( + _accessControlModule != address(0) + && IAccessControlModule(_accessControlModule).hasAccess({ + _caller: msg.sender, + _user: _accessControl.user, + _typehash: _typehash, + _params: _params, + _data: _accessControl.data + }) + ); + if (!_hasAccess) revert IAccessControlData_NoAccess(); + _; + } +} diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 9a57325..d78f4dd 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -10,8 +10,18 @@ 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 {ValidatorLib} from '../libraries/ValidatorLib.sol'; - -contract Oracle is IOracle { +import {AccessController} from './AccessController.sol'; + +import { + _DISPUTE_TYPEHASH, + _ESCALATE_TYPEHASH, + _FINALIZE_TYPEHASH, + _PROPOSE_TYPEHASH, + _RESOLVE_TYPEHASH, + _UPDATE_TYPEHASH +} from './utils/OracleTypehash.sol'; + +contract Oracle is IOracle, AccessController { using ValidatorLib for *; /// @inheritdoc IOracle @@ -44,31 +54,29 @@ contract Oracle is IOracle { /// @inheritdoc IOracle mapping(bytes32 _requestId => mapping(address _user => bool _isParticipant)) public isParticipant; + /// @inheritdoc IOracle + mapping(address _user => mapping(address _accessControlModule => bool _approved)) public isAccessControlApproved; + + /// @inheritdoc IOracle + uint256 public totalRequestCount; + /** * @notice The list of the response ids for each request */ mapping(bytes32 _requestId => bytes _responseIds) internal _responseIds; /// @inheritdoc IOracle - uint256 public totalRequestCount; - - /// @inheritdoc IOracle - function createRequest(Request calldata _request, bytes32 _ipfsHash) external returns (bytes32 _requestId) { - _requestId = _createRequest(_request, _ipfsHash); - } + function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) { + bytes memory _responses = _responseIds[_requestId]; + uint256 _length = _responses.length / 32; - /// @inheritdoc IOracle - function createRequests( - Request[] calldata _requestsData, - bytes32[] calldata _ipfsHashes - ) external returns (bytes32[] memory _batchRequestsIds) { - uint256 _requestsAmount = _requestsData.length; - _batchRequestsIds = new bytes32[](_requestsAmount); + assembly { + for { let _i := 0 } lt(_i, _length) { _i := add(_i, 1) } { + // Increase the size of the array + mstore(_ids, add(mload(_ids), 1)) - for (uint256 _i = 0; _i < _requestsAmount;) { - _batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i]); - unchecked { - ++_i; + // Store the response id in the array + mstore(add(_ids, add(32, mul(_i, 32))), mload(add(_responses, add(32, mul(_i, 32))))) } } } @@ -98,11 +106,37 @@ contract Oracle is IOracle { } } + /// @inheritdoc IOracle + function createRequest(Request calldata _request, bytes32 _ipfsHash) external returns (bytes32 _requestId) { + _requestId = _createRequest(_request, _ipfsHash); + } + + /// @inheritdoc IOracle + function createRequests( + Request[] calldata _requestsData, + bytes32[] calldata _ipfsHashes + ) external returns (bytes32[] memory _batchRequestsIds) { + uint256 _requestsAmount = _requestsData.length; + _batchRequestsIds = new bytes32[](_requestsAmount); + + for (uint256 _i = 0; _i < _requestsAmount;) { + _batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i]); + unchecked { + ++_i; + } + } + } + /// @inheritdoc IOracle function proposeResponse( Request calldata _request, - Response calldata _response - ) external returns (bytes32 _responseId) { + Response calldata _response, + AccessControl calldata _accessControl + ) + external + hasAccess(_request.accessControlModule, _PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) + returns (bytes32 _responseId) + { _responseId = ValidatorLib._validateResponse(_request, _response); bytes32 _requestId = _response.requestId; @@ -112,7 +146,7 @@ contract Oracle is IOracle { } // The caller must be the proposer, unless the response is coming from a dispute module - if (msg.sender != _response.proposer && msg.sender != address(_request.disputeModule)) { + if (_accessControl.user != _response.proposer && _accessControl.user != address(_request.disputeModule)) { revert Oracle_InvalidProposer(); } @@ -125,7 +159,7 @@ contract Oracle is IOracle { revert Oracle_AlreadyFinalized(_requestId); } isParticipant[_requestId][_response.proposer] = true; - IResponseModule(_request.responseModule).propose(_request, _response, msg.sender); + IResponseModule(_request.responseModule).propose(_request, _response, _accessControl.user); _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); responseCreatedAt[_responseId] = block.timestamp; @@ -136,8 +170,13 @@ contract Oracle is IOracle { function disputeResponse( Request calldata _request, Response calldata _response, - Dispute calldata _dispute - ) external returns (bytes32 _disputeId) { + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) + external + hasAccess(_request.accessControlModule, _DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + returns (bytes32 _disputeId) + { bytes32 _responseId; (_responseId, _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -151,7 +190,7 @@ contract Oracle is IOracle { revert Oracle_InvalidProposer(); } - if (_dispute.disputer != msg.sender) { + if (_dispute.disputer != _accessControl.user) { revert Oracle_InvalidDisputer(); } @@ -162,7 +201,7 @@ contract Oracle is IOracle { if (disputeOf[_responseId] != bytes32(0)) { revert Oracle_ResponseAlreadyDisputed(_responseId); } - isParticipant[_requestId][msg.sender] = true; + isParticipant[_requestId][_accessControl.user] = true; disputeStatus[_disputeId] = DisputeStatus.Active; disputeOf[_responseId] = _disputeId; disputeCreatedAt[_disputeId] = block.timestamp; @@ -173,7 +212,15 @@ contract Oracle is IOracle { } /// @inheritdoc IOracle - function escalateDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external { + function escalateDispute( + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) + external + hasAccess(_request.accessControlModule, _ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); if (disputeCreatedAt[_disputeId] == 0) { @@ -194,7 +241,7 @@ contract Oracle is IOracle { // Notify the dispute module about the escalation IDisputeModule(_request.disputeModule).onDisputeStatusChange(_disputeId, _request, _response, _dispute); - emit DisputeEscalated(msg.sender, _disputeId, _dispute); + emit DisputeEscalated(_accessControl.user, _disputeId, _dispute); if (address(_request.resolutionModule) != address(0)) { // Initiate the resolution @@ -203,7 +250,15 @@ contract Oracle is IOracle { } /// @inheritdoc IOracle - function resolveDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external { + function resolveDispute( + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) + external + hasAccess(_request.accessControlModule, _RESOLVE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); if (disputeCreatedAt[_disputeId] == 0) { @@ -226,7 +281,7 @@ contract Oracle is IOracle { IResolutionModule(_request.resolutionModule).resolveDispute(_disputeId, _request, _response, _dispute); - emit DisputeResolved(_disputeId, _dispute, msg.sender); + emit DisputeResolved(_disputeId, _dispute); } /// @inheritdoc IOracle @@ -234,8 +289,17 @@ contract Oracle is IOracle { Request calldata _request, Response calldata _response, Dispute calldata _dispute, - DisputeStatus _status - ) external { + DisputeStatus _status, + AccessControl calldata _accessControl + ) + external + hasAccess( + _request.accessControlModule, + _UPDATE_TYPEHASH, + abi.encode(_request, _response, _dispute, _status), + _accessControl + ) + { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); if (disputeCreatedAt[_disputeId] == 0) { @@ -246,8 +310,11 @@ contract Oracle is IOracle { revert Oracle_InvalidDisputeId(_disputeId); } - if (msg.sender != address(_request.disputeModule) && msg.sender != address(_request.resolutionModule)) { - revert Oracle_NotDisputeOrResolutionModule(msg.sender); + if ( + _accessControl.user != address(_request.disputeModule) + && _accessControl.user != address(_request.resolutionModule) + ) { + revert Oracle_NotDisputeOrResolutionModule(_accessControl.user); } disputeStatus[_disputeId] = _status; IDisputeModule(_request.disputeModule).onDisputeStatusChange(_disputeId, _request, _response, _dispute); @@ -256,23 +323,14 @@ contract Oracle is IOracle { } /// @inheritdoc IOracle - function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) { - bytes memory _responses = _responseIds[_requestId]; - uint256 _length = _responses.length / 32; - - assembly { - for { let _i := 0 } lt(_i, _length) { _i := add(_i, 1) } { - // Increase the size of the array - mstore(_ids, add(mload(_ids), 1)) - - // Store the response id in the array - mstore(add(_ids, add(32, mul(_i, 32))), mload(add(_responses, add(32, mul(_i, 32))))) - } - } - } - - /// @inheritdoc IOracle - function finalize(IOracle.Request calldata _request, IOracle.Response calldata _response) external { + function finalize( + IOracle.Request calldata _request, + IOracle.Response calldata _response, + AccessControl calldata _accessControl + ) + external + hasAccess(_request.accessControlModule, _FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) + { bytes32 _requestId; bytes32 _responseId; @@ -290,18 +348,18 @@ contract Oracle is IOracle { finalizedAt[_requestId] = block.timestamp; if (address(_request.finalityModule) != address(0)) { - IFinalityModule(_request.finalityModule).finalizeRequest(_request, _response, msg.sender); + IFinalityModule(_request.finalityModule).finalizeRequest(_request, _response, _accessControl.user); } if (address(_request.resolutionModule) != address(0)) { - IResolutionModule(_request.resolutionModule).finalizeRequest(_request, _response, msg.sender); + IResolutionModule(_request.resolutionModule).finalizeRequest(_request, _response, _accessControl.user); } - IDisputeModule(_request.disputeModule).finalizeRequest(_request, _response, msg.sender); - IResponseModule(_request.responseModule).finalizeRequest(_request, _response, msg.sender); - IRequestModule(_request.requestModule).finalizeRequest(_request, _response, msg.sender); + IDisputeModule(_request.disputeModule).finalizeRequest(_request, _response, _accessControl.user); + IResponseModule(_request.responseModule).finalizeRequest(_request, _response, _accessControl.user); + IRequestModule(_request.requestModule).finalizeRequest(_request, _response, _accessControl.user); - emit OracleRequestFinalized(_requestId, _responseId, msg.sender); + emit OracleRequestFinalized(_requestId, _responseId); } /** diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol new file mode 100644 index 0000000..5a7e951 --- /dev/null +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +bytes32 constant _PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request, Response _response)'); + +bytes32 constant _DISPUTE_TYPEHASH = + keccak256('DisputeResponse(Request _request, Response _response, Dispute _dispute)'); + +bytes32 constant _ESCALATE_TYPEHASH = + keccak256('EscalateDispute(Request _request, Response _response, Dispute _dispute)'); + +bytes32 constant _RESOLVE_TYPEHASH = keccak256('ResolveDispute(Request _request, Response _response, Dispute _dispute)'); + +bytes32 constant _UPDATE_TYPEHASH = + keccak256('UpdateDisputeStatus(Request _request, Response _response, Dispute _dispute, DisputeStatus _status)'); + +bytes32 constant _FINALIZE_TYPEHASH = keccak256('Finalize(Request _request, Response _response)'); diff --git a/solidity/interfaces/IAccessController.sol b/solidity/interfaces/IAccessController.sol new file mode 100644 index 0000000..0857e24 --- /dev/null +++ b/solidity/interfaces/IAccessController.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +/** + * @title Access Controller Interface + * @notice Interface for the access controller + */ +interface IAccessController { + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + /** + * @notice The access control struct + * @param user The address of the user + * @param data The data for access control validation + */ + struct AccessControl { + address user; + bytes data; + } + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Reverts if the caller has no access + */ + error IAccessControlData_NoAccess(); +} diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 2e30b14..cfd8837 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import {IAccessController} from './IAccessController.sol'; + /** * @title Oracle * @notice The main contract storing requests, responses and disputes, and routing the calls to the modules. */ -interface IOracle { +interface IOracle is IAccessController { /*/////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ @@ -38,9 +40,8 @@ interface IOracle { * @notice Emitted when a request is finalized * @param _requestId The id of the request being finalized * @param _responseId The id of the final response, may be empty - * @param _caller The address of the user who finalized the request */ - event OracleRequestFinalized(bytes32 indexed _requestId, bytes32 indexed _responseId, address indexed _caller); + event OracleRequestFinalized(bytes32 indexed _requestId, bytes32 indexed _responseId); /** * @notice Emitted when a dispute is escalated @@ -62,9 +63,8 @@ interface IOracle { * @notice Emitted when a dispute is resolved * @param _disputeId The id of the dispute being resolved * @param _dispute The dispute that is being updated - * @param _caller The address of the user who resolved the dispute */ - event DisputeResolved(bytes32 indexed _disputeId, Dispute _dispute, address indexed _caller); + event DisputeResolved(bytes32 indexed _disputeId, Dispute _dispute); /*/////////////////////////////////////////////////////////////// ERRORS @@ -196,11 +196,13 @@ interface IOracle { * @param disputeModule The address of the dispute module * @param resolutionModule The address of the resolution module * @param finalityModule The address of the finality module + * @param accessControlModule The address of the access control module * @param requestModuleData The parameters for the request module * @param responseModuleData The parameters for the response module * @param disputeModuleData The parameters for the dispute module * @param resolutionModuleData The parameters for the resolution module * @param finalityModuleData The parameters for the finality module + * @param accessControlModuleData The parameters for the access control module * @param requester The address of the user who created the request * @param nonce The nonce of the request */ @@ -212,11 +214,13 @@ interface IOracle { address disputeModule; address resolutionModule; address finalityModule; + address accessControlModule; bytes requestModuleData; bytes responseModuleData; bytes disputeModuleData; bytes resolutionModuleData; bytes finalityModuleData; + bytes accessControlModuleData; } /** @@ -250,19 +254,44 @@ interface IOracle { //////////////////////////////////////////////////////////////*/ /** - * @notice Returns the dispute id for a given response + * @notice The block's timestamp at which a request was finalized * - * @param _responseId The response id to get the dispute for - * @return _disputeId The id of the dispute associated with the given response + * @param _requestId The request id + * @return _finalizedAt The block's timestamp */ - function disputeOf(bytes32 _responseId) external view returns (bytes32 _disputeId); + function finalizedAt(bytes32 _requestId) external view returns (uint256 _finalizedAt); /** - * @notice Returns the total number of requests stored in the oracle + * @notice The block's timestamp at which a request was created * - * @return _count The total number of requests + * @param _id The request id + * @return _requestCreatedAt The block's timestamp */ - function totalRequestCount() external view returns (uint256 _count); + function requestCreatedAt(bytes32 _id) external view returns (uint256 _requestCreatedAt); + + /** + * @notice The block's timestamp at which a response was created + * + * @param _id The response id + * @return _responseCreatedAt The block's timestamp + */ + function responseCreatedAt(bytes32 _id) external view returns (uint256 _responseCreatedAt); + + /** + * @notice The block's timestamp at which a dispute was created + * + * @param _id The dispute id + * @return _disputeCreatedAt The block's timestamp + */ + function disputeCreatedAt(bytes32 _id) external view returns (uint256 _disputeCreatedAt); + + /** + * @notice Returns the dispute id for a given response + * + * @param _responseId The response id to get the dispute for + * @return _disputeId The id of the dispute associated with the given response + */ + function disputeOf(bytes32 _responseId) external view returns (bytes32 _disputeId); /** * @notice Returns the status of a dispute @@ -289,36 +318,55 @@ interface IOracle { function finalizedResponseId(bytes32 _requestId) external view returns (bytes32 _finalizedResponseId); /** - * @notice The block's timestamp at which a request was created + * @notice Checks if the given address is a module used in the request * - * @param _id The request id - * @return _requestCreatedAt The block's timestamp + * @param _requestId The id of the request + * @param _module The address to check + * @return _allowed If the module is a part of the request */ - function requestCreatedAt(bytes32 _id) external view returns (uint256 _requestCreatedAt); + function allowedModule(bytes32 _requestId, address _module) external view returns (bool _allowed); /** - * @notice The block's timestamp at which a response was created + * @notice Checks if the given address is participating in a specific request * - * @param _id The response id - * @return _responseCreatedAt The block's timestamp + * @param _requestId The id of the request + * @param _user The address to check + * @return _isParticipant If the user is a participant of the request */ - function responseCreatedAt(bytes32 _id) external view returns (uint256 _responseCreatedAt); + function isParticipant(bytes32 _requestId, address _user) external view returns (bool _isParticipant); /** - * @notice The block's timestamp at which a dispute was created + * @notice Checks if the given address approved the access control module * - * @param _id The dispute id - * @return _disputeCreatedAt The block's timestamp + * @param _user The address to check + * @param _accessControlModule The address of the access control module + * @return _approved If the user approved the access control module */ - function disputeCreatedAt(bytes32 _id) external view returns (uint256 _disputeCreatedAt); + function isAccessControlApproved(address _user, address _accessControlModule) external view returns (bool _approved); /** - * @notice The block's timestamp at which a request was finalized + * @notice Returns the total number of requests stored in the oracle * - * @param _requestId The request id - * @return _finalizedAt The block's timestamp + * @return _count The total number of requests */ - function finalizedAt(bytes32 _requestId) external view returns (uint256 _finalizedAt); + function totalRequestCount() external view returns (uint256 _count); + + /** + * @notice Returns the list of request IDs + * + * @param _startFrom The index to start from + * @param _batchSize The number of requests to return + * @return _list The list of request IDs + */ + function listRequestIds(uint256 _startFrom, uint256 _batchSize) external view returns (bytes32[] memory _list); + + /** + * @notice Returns the ids of the responses for a given request + * + * @param _requestId The id of the request + * @return _ids The ids of the responses + */ + function getResponseIds(bytes32 _requestId) external view returns (bytes32[] memory _ids); /*/////////////////////////////////////////////////////////////// LOGIC @@ -346,25 +394,18 @@ interface IOracle { bytes32[] calldata _ipfsHashes ) external returns (bytes32[] memory _batchRequestsIds); - /** - * @notice Returns the list of request IDs - * - * @param _startFrom The index to start from - * @param _batchSize The number of requests to return - * @return _list The list of request IDs - */ - function listRequestIds(uint256 _startFrom, uint256 _batchSize) external view returns (bytes32[] memory _list); - /** * @notice Creates a new response for a given request * * @param _request The request to create a response for * @param _response The response data + * @param _accessControl The access control data * @return _responseId The id of the created response */ function proposeResponse( Request calldata _request, - Response calldata _response + Response calldata _response, + AccessControl calldata _accessControl ) external returns (bytes32 _responseId); /** @@ -373,12 +414,14 @@ interface IOracle { * @param _request The request * @param _response The response to dispute * @param _dispute The dispute data + * @param _accessControl The access control data * @return _disputeId The id of the created dispute */ function disputeResponse( Request calldata _request, Response calldata _response, - Dispute calldata _dispute + Dispute calldata _dispute, + AccessControl calldata _accessControl ) external returns (bytes32 _disputeId); /** @@ -387,8 +430,14 @@ interface IOracle { * @param _request The request * @param _response The disputed response * @param _dispute The dispute that is being escalated + * @param _accessControl The access control data */ - function escalateDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external; + function escalateDispute( + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) external; /** * @notice Resolves a dispute @@ -396,8 +445,14 @@ interface IOracle { * @param _request The request * @param _response The disputed response * @param _dispute The dispute that is being resolved + * @param _accessControl The access control data */ - function resolveDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external; + function resolveDispute( + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) external; /** * @notice Updates the status of a dispute @@ -406,46 +461,27 @@ interface IOracle { * @param _response The disputed response * @param _dispute The dispute that is being updated * @param _status The new status of the dispute + * @param _accessControl The access control data */ function updateDisputeStatus( Request calldata _request, Response calldata _response, Dispute calldata _dispute, - DisputeStatus _status + DisputeStatus _status, + AccessControl calldata _accessControl ) external; - /** - * @notice Checks if the given address is a module used in the request - * - * @param _requestId The id of the request - * @param _module The address to check - * @return _allowedModule If the module is a part of the request - */ - function allowedModule(bytes32 _requestId, address _module) external view returns (bool _allowedModule); - - /** - * @notice Checks if the given address is participating in a specific request - * - * @param _requestId The id of the request - * @param _user The address to check - * @return _isParticipant If the user is a participant of the request - */ - function isParticipant(bytes32 _requestId, address _user) external view returns (bool _isParticipant); - - /** - * @notice Returns the ids of the responses for a given request - * - * @param _requestId The id of the request - * @return _ids The ids of the responses - */ - function getResponseIds(bytes32 _requestId) external view returns (bytes32[] memory _ids); - /** * @notice Finalizes the request and executes the post-request logic on the modules * * @dev In case of a request with no responses, an response with am empty `requestId` is expected * @param _request The request being finalized * @param _response The final response + * @param _accessControl The access control data */ - function finalize(Request calldata _request, Response calldata _response) external; + function finalize( + Request calldata _request, + Response calldata _response, + AccessControl calldata _accessControl + ) external; } diff --git a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol new file mode 100644 index 0000000..1868425 --- /dev/null +++ b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IModule} from '../../IModule.sol'; + +/** + * @title ResponseModule + * @notice Common interface for all response modules + */ +interface IAccessControlModule is IModule { + /** + * @notice Checks if the caller has access to the user + * @param _caller The caller address + * @param _user The user address + * @param _typehash The typehash of the request + * @param _params The parameters of the request + * @param _data The data for access control validation + * @return _hasAccess True if the caller has access to the user + */ + function hasAccess( + address _caller, + address _user, + bytes32 _typehash, + bytes memory _params, + bytes calldata _data + ) external returns (bool _hasAccess); +} diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index cfe7bda..31a50be 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -1,31 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import './IntegrationBase.sol'; +// import './IntegrationBase.sol'; -contract Integration_EscalateDispute is IntegrationBase { - function test_escalateDispute() public { - // Create the request - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); +// contract Integration_EscalateDispute is IntegrationBase { +// function test_escalateDispute() public { +// // Create the request +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); - // Submit a response - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); +// // Submit a response +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); - // Dispute the response - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// // Dispute the response +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - // We escalate the dispute - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); +// // We escalate the dispute +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - // We check that the dispute was escalated - bytes32 _disputeId = _getId(mockDispute); - assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.Escalated); +// // We check that the dispute was escalated +// bytes32 _disputeId = _getId(mockDispute); +// assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.Escalated); - // Escalate dispute reverts if dispute is not active - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - } -} +// // Escalate dispute reverts if dispute is not active +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); +// } +// } diff --git a/solidity/test/integration/Finalization.t.sol b/solidity/test/integration/Finalization.t.sol index 4699b19..ca35c5a 100644 --- a/solidity/test/integration/Finalization.t.sol +++ b/solidity/test/integration/Finalization.t.sol @@ -1,139 +1,139 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import './IntegrationBase.sol'; - -contract Integration_Finalization is IntegrationBase { - address internal _finalizer = makeAddr('finalizer'); - address internal _callbackTarget = makeAddr('target'); - - function setUp() public override { - super.setUp(); - vm.etch(_callbackTarget, hex'069420'); - } - - /** - * @notice Test to check if another module can be set as callback module. - */ - function test_targetIsAnotherModule() public { - mockRequest.finalityModuleData = abi.encode( - IMockFinalityModule.RequestParameters({ - target: address(_finalityModule), - data: abi.encodeWithSignature('callback()') - }) - ); - - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - - _jumpToFinalization(); - - vm.warp(block.timestamp + _baseDisputeWindow); - vm.prank(_finalizer); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Test to check that finalization data is set and callback calls are made. - */ - function test_makeAndIgnoreLowLevelCalls(bytes memory _calldata) public { - mockRequest.finalityModuleData = - abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); - - vm.prank(requester); - bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash); - - _jumpToFinalization(); - - // Check: all low-level calls are made? - vm.expectCall(_callbackTarget, _calldata); - - vm.warp(block.timestamp + _baseDisputeWindow); - vm.prank(_finalizer); - oracle.finalize(mockRequest, mockResponse); - - bytes32 _responseId = oracle.finalizedResponseId(_requestId); - // Check: is request finalized? - assertEq(_responseId, _getId(mockResponse)); - } - - /** - * @notice Test to check that finalizing a request that has no response will succeed. - */ - function test_finalizeWithoutResponse() public { - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - - mockResponse.requestId = bytes32(0); - - // Check: finalizes if request has no response? - vm.prank(_finalizer); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Test to check that finalizing a request with a ongoing dispute will revert. - */ - function test_revertFinalizeWithDisputedResponse() public { - mockRequest.finalityModuleData = - abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); - - mockResponse.requestId = _getId(mockRequest); - mockDispute.requestId = mockResponse.requestId; - mockDispute.responseId = _getId(mockResponse); - - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); - - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - - vm.prank(_finalizer); - vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Test to check that finalizing a request with a ongoing dispute will revert. - */ - function test_revertFinalizeInDisputeWindow() public { - mockRequest.finalityModuleData = - abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); - - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - } - - /** - * @notice Test to check that finalizing a request without disputes triggers callback calls and executes without reverting. - */ - function test_finalizeWithUndisputedResponse(bytes calldata _calldata) public { - mockRequest.finalityModuleData = - abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); - - vm.expectCall(_callbackTarget, _calldata); - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - - _jumpToFinalization(); - - vm.warp(block.timestamp + _baseDisputeWindow); - vm.prank(_finalizer); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Internal helper function to setup the finalization stage of a request. - */ - function _jumpToFinalization() internal returns (bytes32 _responseId) { - mockResponse.requestId = _getId(mockRequest); - - vm.prank(proposer); - _responseId = oracle.proposeResponse(mockRequest, mockResponse); - - vm.warp(_expectedDeadline + 1); - } -} +// import './IntegrationBase.sol'; + +// contract Integration_Finalization is IntegrationBase { +// address internal _finalizer = makeAddr('finalizer'); +// address internal _callbackTarget = makeAddr('target'); + +// function setUp() public override { +// super.setUp(); +// vm.etch(_callbackTarget, hex'069420'); +// } + +// /** +// * @notice Test to check if another module can be set as callback module. +// */ +// function test_targetIsAnotherModule() public { +// mockRequest.finalityModuleData = abi.encode( +// IMockFinalityModule.RequestParameters({ +// target: address(_finalityModule), +// data: abi.encodeWithSignature('callback()') +// }) +// ); + +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); + +// _jumpToFinalization(); + +// vm.warp(block.timestamp + _baseDisputeWindow); +// vm.prank(_finalizer); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Test to check that finalization data is set and callback calls are made. +// */ +// function test_makeAndIgnoreLowLevelCalls(bytes memory _calldata) public { +// mockRequest.finalityModuleData = +// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); + +// vm.prank(requester); +// bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash); + +// _jumpToFinalization(); + +// // Check: all low-level calls are made? +// vm.expectCall(_callbackTarget, _calldata); + +// vm.warp(block.timestamp + _baseDisputeWindow); +// vm.prank(_finalizer); +// oracle.finalize(mockRequest, mockResponse); + +// bytes32 _responseId = oracle.finalizedResponseId(_requestId); +// // Check: is request finalized? +// assertEq(_responseId, _getId(mockResponse)); +// } + +// /** +// * @notice Test to check that finalizing a request that has no response will succeed. +// */ +// function test_finalizeWithoutResponse() public { +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); + +// mockResponse.requestId = bytes32(0); + +// // Check: finalizes if request has no response? +// vm.prank(_finalizer); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Test to check that finalizing a request with a ongoing dispute will revert. +// */ +// function test_revertFinalizeWithDisputedResponse() public { +// mockRequest.finalityModuleData = +// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); + +// mockResponse.requestId = _getId(mockRequest); +// mockDispute.requestId = mockResponse.requestId; +// mockDispute.responseId = _getId(mockResponse); + +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); + +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); + +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); + +// vm.prank(_finalizer); +// vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Test to check that finalizing a request with a ongoing dispute will revert. +// */ +// function test_revertFinalizeInDisputeWindow() public { +// mockRequest.finalityModuleData = +// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); + +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); +// } + +// /** +// * @notice Test to check that finalizing a request without disputes triggers callback calls and executes without reverting. +// */ +// function test_finalizeWithUndisputedResponse(bytes calldata _calldata) public { +// mockRequest.finalityModuleData = +// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); + +// vm.expectCall(_callbackTarget, _calldata); +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); + +// _jumpToFinalization(); + +// vm.warp(block.timestamp + _baseDisputeWindow); +// vm.prank(_finalizer); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Internal helper function to setup the finalization stage of a request. +// */ +// function _jumpToFinalization() internal returns (bytes32 _responseId) { +// mockResponse.requestId = _getId(mockRequest); + +// vm.prank(proposer); +// _responseId = oracle.proposeResponse(mockRequest, mockResponse); + +// vm.warp(_expectedDeadline + 1); +// } +// } diff --git a/solidity/test/integration/ResponseDispute.t.sol b/solidity/test/integration/ResponseDispute.t.sol index dd4743c..df81ff5 100644 --- a/solidity/test/integration/ResponseDispute.t.sol +++ b/solidity/test/integration/ResponseDispute.t.sol @@ -1,48 +1,48 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import './IntegrationBase.sol'; +// import './IntegrationBase.sol'; -contract Integration_ResponseDispute is IntegrationBase { - bytes internal _responseData; - bytes32 internal _requestId; - bytes32 internal _responseId; +// contract Integration_ResponseDispute is IntegrationBase { +// bytes internal _responseData; +// bytes32 internal _requestId; +// bytes32 internal _responseId; - function setUp() public override { - super.setUp(); +// function setUp() public override { +// super.setUp(); - _expectedDeadline = block.timestamp + BLOCK_TIME * 600; - _responseData = abi.encode('response'); +// _expectedDeadline = block.timestamp + BLOCK_TIME * 600; +// _responseData = abi.encode('response'); - mockRequest.nonce = uint96(oracle.totalRequestCount()); +// mockRequest.nonce = uint96(oracle.totalRequestCount()); - vm.prank(requester); - _requestId = oracle.createRequest(mockRequest, _ipfsHash); +// vm.prank(requester); +// _requestId = oracle.createRequest(mockRequest, _ipfsHash); - mockResponse.requestId = _requestId; +// mockResponse.requestId = _requestId; - vm.prank(proposer); - _responseId = oracle.proposeResponse(mockRequest, mockResponse); - } +// vm.prank(proposer); +// _responseId = oracle.proposeResponse(mockRequest, mockResponse); +// } - function test_disputeResponse_alreadyFinalized() public { - vm.warp(_expectedDeadline + _baseDisputeWindow); - oracle.finalize(mockRequest, mockResponse); +// function test_disputeResponse_alreadyFinalized() public { +// vm.warp(_expectedDeadline + _baseDisputeWindow); +// oracle.finalize(mockRequest, mockResponse); - vm.prank(disputer); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - } +// vm.prank(disputer); +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// } - function test_disputeResponse_alreadyDisputed() public { - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// function test_disputeResponse_alreadyDisputed() public { +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - address _anotherDisputer = makeAddr('anotherDisputer'); - mockDispute.disputer = _anotherDisputer; +// address _anotherDisputer = makeAddr('anotherDisputer'); +// mockDispute.disputer = _anotherDisputer; - vm.prank(_anotherDisputer); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - } -} +// vm.prank(_anotherDisputer); +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// } +// } diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index 863454d..9b3675a 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -1,45 +1,45 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import './IntegrationBase.sol'; +// import './IntegrationBase.sol'; -contract Integration_ResponseProposal is IntegrationBase { - bytes32 internal _requestId; +// contract Integration_ResponseProposal is IntegrationBase { +// bytes32 internal _requestId; - function setUp() public override { - super.setUp(); +// function setUp() public override { +// super.setUp(); - mockRequest.nonce = uint96(oracle.totalRequestCount()); +// mockRequest.nonce = uint96(oracle.totalRequestCount()); - vm.prank(requester); - _requestId = oracle.createRequest(mockRequest, _ipfsHash); - } +// vm.prank(requester); +// _requestId = oracle.createRequest(mockRequest, _ipfsHash); +// } - function test_proposeResponse_validResponse(bytes memory _response) public { - mockResponse.response = _response; +// function test_proposeResponse_validResponse(bytes memory _response) public { +// mockResponse.response = _response; - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); - bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); +// bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); - // Check: response data is correctly stored? - assertEq(_responseIds.length, 1); - assertEq(_responseIds[0], _getId(mockResponse)); - } +// // Check: response data is correctly stored? +// assertEq(_responseIds.length, 1); +// assertEq(_responseIds[0], _getId(mockResponse)); +// } - function test_proposeResponse_finalizedRequest(uint256 _timestamp) public { - _timestamp = bound(_timestamp, _expectedDeadline + _baseDisputeWindow, type(uint128).max); +// function test_proposeResponse_finalizedRequest(uint256 _timestamp) public { +// _timestamp = bound(_timestamp, _expectedDeadline + _baseDisputeWindow, type(uint128).max); - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); - vm.warp(_timestamp); - oracle.finalize(mockRequest, mockResponse); +// vm.warp(_timestamp); +// oracle.finalize(mockRequest, mockResponse); - mockResponse.response = abi.encode(_timestamp); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); - } -} +// mockResponse.response = abi.encode(_timestamp); +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); +// } +// } diff --git a/solidity/test/mocks/contracts/MockAtomicArbitrator.sol b/solidity/test/mocks/contracts/MockAtomicArbitrator.sol index a807c10..b7af1a2 100644 --- a/solidity/test/mocks/contracts/MockAtomicArbitrator.sol +++ b/solidity/test/mocks/contracts/MockAtomicArbitrator.sol @@ -18,7 +18,7 @@ contract MockAtomicArbitrator { ) external returns (bytes memory _result) { _result = new bytes(0); answer = IOracle.DisputeStatus.Won; - oracle.resolveDispute(_request, _response, _dispute); + // oracle.resolveDispute(_request, _response, _dispute); } function getAnswer(bytes32 /* _dispute */ ) external view returns (IOracle.DisputeStatus _answer) { diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 872ee49..91dc30e 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -1,1261 +1,1261 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import 'forge-std/Test.sol'; - -import {IModule} from '../../interfaces/IModule.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 {Oracle} from '../../contracts/Oracle.sol'; -import {Helpers} from '../utils/Helpers.sol'; - -import {ValidatorLib} from '../../libraries/ValidatorLib.sol'; - -/** - * @notice Harness to deploy and test Oracle - */ -contract MockOracle is Oracle { - constructor() Oracle() {} - - function mock_addParticipant(bytes32 _requestId, address _participant) external { - isParticipant[_requestId][_participant] = true; - } - - function mock_addAllowedModule(bytes32 _requestId, address _module) external { - allowedModule[_requestId][_module] = true; - } - - function mock_setFinalizedResponseId(bytes32 _requestId, bytes32 _finalizedResponseId) external { - finalizedResponseId[_requestId] = _finalizedResponseId; - } - - function mock_setFinalizedAt(bytes32 _requestId, uint256 _finalizedAt) external { - finalizedAt[_requestId] = _finalizedAt; - } - - function mock_setDisputeOf(bytes32 _responseId, bytes32 _disputeId) external { - disputeOf[_responseId] = _disputeId; - } - - function mock_setDisputeStatus(bytes32 _disputeId, IOracle.DisputeStatus _status) external { - disputeStatus[_disputeId] = _status; - } - - function mock_setRequestId(uint256 _nonce, bytes32 _requestId) external { - nonceToRequestId[_nonce] = _requestId; - } - - function mock_setRequestCreatedAt(bytes32 _requestId, uint256 _requestCreatedAt) external { - requestCreatedAt[_requestId] = _requestCreatedAt; - } - - function mock_setResponseCreatedAt(bytes32 _responseId, uint256 _responseCreatedAt) external { - responseCreatedAt[_responseId] = _responseCreatedAt; - } - - function mock_setDisputeCreatedAt(bytes32 _disputeId, uint256 _disputeCreatedAt) external { - disputeCreatedAt[_disputeId] = _disputeCreatedAt; - } - - function mock_setTotalRequestCount(uint256 _totalRequestCount) external { - totalRequestCount = _totalRequestCount; - } - - function mock_addResponseId(bytes32 _requestId, bytes32 _responseId) external { - _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); - } -} - -/** - * @title Oracle Unit tests - */ -contract BaseTest is Test, Helpers { - // The target contract - MockOracle public 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')); - - // Mock IPFS hash - bytes32 internal _ipfsHash = bytes32('QmR4uiJH654k3Ta2uLLQ8r'); - - // Events - event RequestCreated(bytes32 indexed _requestId, IOracle.Request _request, bytes32 _ipfsHash); - event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, IOracle.Response _response); - event ResponseDisputed(bytes32 indexed _responseId, bytes32 indexed _disputeId, IOracle.Dispute _dispute); - event OracleRequestFinalized(bytes32 indexed _requestId, bytes32 indexed _responseId, address indexed _caller); - event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); - event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); - event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute, address indexed _caller); - - function setUp() public virtual { - oracle = new MockOracle(); - - 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); - } - - /** - * @notice If no dispute and finality module used, set them to address(0) - */ - modifier setResolutionAndFinality(bool _useResolutionAndFinality) { - if (!_useResolutionAndFinality) { - resolutionModule = IResolutionModule(address(0)); - finalityModule = IFinalityModule(address(0)); - } - _; - } -} - -contract Oracle_Unit_CreateRequest is BaseTest { - /** - * @notice Test the request creation with correct arguments and nonce increment - * @dev The request might or might not use a dispute and a finality module, this is fuzzed - */ - function test_createRequest( - bool _useResolutionAndFinality, - bytes calldata _requestData, - bytes calldata _responseData, - bytes calldata _disputeData, - bytes calldata _resolutionData, - bytes calldata _finalityData - ) public setResolutionAndFinality(_useResolutionAndFinality) { - uint256 _initialNonce = oracle.totalRequestCount(); - - // Create the request - mockRequest.requestModuleData = _requestData; - mockRequest.responseModuleData = _responseData; - mockRequest.disputeModuleData = _disputeData; - mockRequest.resolutionModuleData = _resolutionData; - mockRequest.finalityModuleData = _finalityData; - mockRequest.requester = requester; - mockRequest.nonce = uint96(oracle.totalRequestCount()); - - // Compute the associated request id - bytes32 _theoreticalRequestId = _getId(mockRequest); - - // Check: emits RequestCreated event? - _expectEmit(address(oracle)); - emit RequestCreated(_getId(mockRequest), mockRequest, _ipfsHash); - - // Test: create the request - vm.prank(requester); - bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash); - - // Check: Adds the requester to the list of participants - assertTrue(oracle.isParticipant(_requestId, requester)); - - // Check: Saves the number of the block - assertEq(oracle.requestCreatedAt(_requestId), block.timestamp); - - // Check: Sets allowedModules - assertTrue(oracle.allowedModule(_requestId, address(requestModule))); - assertTrue(oracle.allowedModule(_requestId, address(responseModule))); - assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); - - if (_useResolutionAndFinality) { - assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); - assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); - } - - // Check: Maps the nonce to the requestId - assertEq(oracle.nonceToRequestId(mockRequest.nonce), _requestId); - - // Check: correct request id returned? - assertEq(_requestId, _theoreticalRequestId); - - // Check: nonce incremented? - assertEq(oracle.totalRequestCount(), _initialNonce + 1); - } - - /** - * @notice Check that creating a request with a nonce that already exists reverts - */ - function test_createRequest_revertsIfInvalidNonce(uint256 _nonce) public { - vm.assume(_nonce != oracle.totalRequestCount()); - - // Set the nonce - mockRequest.nonce = uint96(_nonce); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); - - // Test: try to create the request - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - } - - /** - * @notice Check that creating a request with a misconfigured requester reverts - */ - function test_createRequest_revertsIfInvalidRequester(address _requester) public { - vm.assume(_requester != requester); - - // Set the nonce - mockRequest.requester = _requester; - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); - - // Test: try to create the request - vm.prank(requester); - oracle.createRequest(mockRequest, _ipfsHash); - } -} - -contract Oracle_Unit_CreateRequests is BaseTest { - /** - * @notice Test creation of requests in batch mode. - */ - function test_createRequests( - bytes calldata _requestData, - bytes calldata _responseData, - bytes calldata _disputeData - ) public { - uint256 _initialNonce = oracle.totalRequestCount(); - uint256 _requestsAmount = 5; - IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); - bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); - bool _useResolutionAndFinality = _requestData.length % 2 == 0; - bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); - - // Generate requests batch - for (uint256 _i = 0; _i < _requestsAmount; _i++) { - mockRequest.requestModuleData = _requestData; - mockRequest.responseModuleData = _responseData; - mockRequest.disputeModuleData = _disputeData; - mockRequest.requester = requester; - mockRequest.nonce = uint96(oracle.totalRequestCount() + _i); - - bytes32 _theoreticalRequestId = _getId(mockRequest); - _requests[_i] = mockRequest; - _precalculatedIds[_i] = _theoreticalRequestId; - _ipfsHashes[_i] = keccak256(abi.encode(_theoreticalRequestId, mockRequest.nonce)); - - // Check: emits RequestCreated event? - _expectEmit(address(oracle)); - emit RequestCreated(_theoreticalRequestId, mockRequest, _ipfsHashes[_i]); - } - - vm.prank(requester); - bytes32[] memory _requestsIds = oracle.createRequests(_requests, _ipfsHashes); - - for (uint256 _i = 0; _i < _requestsIds.length; _i++) { - assertEq(_requestsIds[_i], _precalculatedIds[_i]); - - // Check: Adds the requester to the list of participants - assertTrue(oracle.isParticipant(_requestsIds[_i], requester)); - - // Check: Saves the number of the block - assertEq(oracle.requestCreatedAt(_requestsIds[_i]), block.timestamp); - - // Check: Sets allowedModules - assertTrue(oracle.allowedModule(_requestsIds[_i], address(requestModule))); - assertTrue(oracle.allowedModule(_requestsIds[_i], address(responseModule))); - assertTrue(oracle.allowedModule(_requestsIds[_i], address(disputeModule))); - - if (_useResolutionAndFinality) { - assertTrue(oracle.allowedModule(_requestsIds[_i], address(resolutionModule))); - assertTrue(oracle.allowedModule(_requestsIds[_i], address(finalityModule))); - } - - // Check: Maps the nonce to the requestId - assertEq(oracle.nonceToRequestId(_requests[_i].nonce), _requestsIds[_i]); - } - - uint256 _newNonce = oracle.totalRequestCount(); - assertEq(_newNonce, _initialNonce + _requestsAmount); - } - - /** - * @notice Test creation of requests in batch mode with nonce 0. - */ - function test_createRequestsWithNonceZero( - bytes calldata _requestData, - bytes calldata _responseData, - bytes calldata _disputeData - ) public { - uint256 _initialNonce = oracle.totalRequestCount(); - uint256 _requestsAmount = 5; - IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); - bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); - bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); - - mockRequest.requestModuleData = _requestData; - mockRequest.responseModuleData = _responseData; - mockRequest.disputeModuleData = _disputeData; - mockRequest.requester = requester; - mockRequest.nonce = uint96(0); - - bytes32 _theoreticalRequestId = _getId(mockRequest); - bytes32 _ipfsHash = keccak256(abi.encode(_theoreticalRequestId, uint96(0))); - - // Generate requests batch - for (uint256 _i = 0; _i < _requestsAmount; _i++) { - _requests[_i] = mockRequest; - _precalculatedIds[_i] = _theoreticalRequestId; - _ipfsHashes[_i] = _ipfsHash; - } - - vm.prank(requester); - oracle.createRequests(_requests, _ipfsHashes); - - uint256 _newNonce = oracle.totalRequestCount(); - assertEq(_newNonce, _initialNonce + _requestsAmount); - } -} - -contract Oracle_Unit_ListRequestIds is BaseTest { - /** - * @notice Test list requests ids, fuzz the batch size - */ - function test_listRequestIds(uint256 _numberOfRequests) public { - // 0 to 10 request to list, fuzzed - _numberOfRequests = bound(_numberOfRequests, 0, 10); - - bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); - - for (uint256 _i; _i < _numberOfRequests; _i++) { - mockRequest.nonce = uint96(_i); - bytes32 _requestId = _getId(mockRequest); - _mockRequestIds[_i] = _requestId; - oracle.mock_setRequestId(_i, _requestId); - } - - oracle.mock_setTotalRequestCount(_numberOfRequests); - - // Test: fetching the requests - bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); - - // Check: enough request returned? - assertEq(_requestsIds.length, _numberOfRequests); - - // Check: correct requests returned (dummy are incremented)? - for (uint256 _i; _i < _numberOfRequests; _i++) { - assertEq(_requestsIds[_i], _mockRequestIds[_i]); - } - } - - /** - * @notice Test the request listing if asking for more request than it exists - */ - function test_listRequestIds_tooManyRequested(uint256 _numberOfRequests) public { - // 1 to 10 request to list, fuzzed - _numberOfRequests = bound(_numberOfRequests, 1, 10); - - bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); - - for (uint256 _i; _i < _numberOfRequests; _i++) { - mockRequest.nonce = uint96(_i); - bytes32 _requestId = _getId(mockRequest); - _mockRequestIds[_i] = _requestId; - oracle.mock_setRequestId(_i, _requestId); - } - - oracle.mock_setTotalRequestCount(_numberOfRequests); - - // Test: fetching 1 extra request - bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests + 1); - - // Check: correct number of request returned? - assertEq(_requestsIds.length, _numberOfRequests); - - // Check: correct data? - for (uint256 _i; _i < _numberOfRequests; _i++) { - assertEq(_requestsIds[_i], _mockRequestIds[_i]); - } - - // Test: starting from an index outside of the range - _requestsIds = oracle.listRequestIds(_numberOfRequests + 1, _numberOfRequests); - assertEq(_requestsIds.length, 0); - } - - /** - * @notice Test the request listing if there are no requests encoded - */ - function test_listRequestIds_zeroToReturn(uint256 _numberOfRequests) public { - // Test: fetch any number of requests - bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); - - // Check; 0 returned? - assertEq(_requestsIds.length, 0); - } -} - -contract Oracle_Unit_ProposeResponse is BaseTest { - /** - * @notice Proposing a response should call the response module, emit an event and return the response id - */ - function test_proposeResponse(bytes calldata _responseData) public { - bytes32 _requestId = _getId(mockRequest); - - // Update mock response - mockResponse.response = _responseData; - - // Compute the response ID - bytes32 _responseId = _getId(mockResponse); - - // Set the request creation time - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - - // Mock and expect the responseModule propose call: - _mockAndExpect( - address(responseModule), - abi.encodeCall(IResponseModule.propose, (mockRequest, mockResponse, proposer)), - abi.encode(mockResponse) - ); - - // Check: emits ResponseProposed event? - _expectEmit(address(oracle)); - emit ResponseProposed(_requestId, _responseId, mockResponse); - - // Test: propose the response - vm.prank(proposer); - bytes32 _actualResponseId = oracle.proposeResponse(mockRequest, mockResponse); - - mockResponse.response = bytes('secondResponse'); - - // Check: emits ResponseProposed event? - _expectEmit(address(oracle)); - emit ResponseProposed(_requestId, _getId(mockResponse), mockResponse); - - vm.prank(proposer); - bytes32 _secondResponseId = oracle.proposeResponse(mockRequest, mockResponse); - - // Check: correct response id returned? - assertEq(_actualResponseId, _responseId); - - // Check: responseId are unique? - assertNotEq(_secondResponseId, _responseId); - - // Check: correct response id stored in the id list and unique? - bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); - assertEq(_responseIds.length, 2); - assertEq(_responseIds[0], _responseId); - assertEq(_responseIds[1], _secondResponseId); - } - - function test_proposeResponse_revertsIfInvalidRequest() public { - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidRequest.selector); - - // Test: try to propose a response with an invalid request - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); - } - - /** - * @notice Revert if the caller is not the proposer nor the dispute module - */ - function test_proposeResponse_revertsIfInvalidCaller(address _caller) public { - vm.assume(_caller != proposer && _caller != address(disputeModule)); - - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); - - // Test: try to propose a response from a random address - vm.prank(_caller); - oracle.proposeResponse(mockRequest, mockResponse); - } - - /** - * @notice Revert if the response has been already proposed - */ - function test_proposeResponse_revertsIfDuplicateResponse() public { - // Set the request creation time - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - - // Test: propose a response - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_ResponseAlreadyProposed.selector); - - // Test: try to propose the same response again - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); - } - - /** - * @notice Proposing a response to a finalized request should fail - */ - function test_proposeResponse_revertsIfAlreadyFinalized(uint128 _finalizedAt) public { - vm.assume(_finalizedAt > 0); - - // Set the finalization time - bytes32 _requestId = _getId(mockRequest); - oracle.mock_setFinalizedAt(_requestId, _finalizedAt); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - - // Check: Reverts if already finalized? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, (_requestId))); - vm.prank(proposer); - oracle.proposeResponse(mockRequest, mockResponse); - } -} - -contract Oracle_Unit_DisputeResponse is BaseTest { - bytes32 internal _responseId; - bytes32 internal _disputeId; - - function setUp() public override { - super.setUp(); - - _responseId = _getId(mockResponse); - _disputeId = _getId(mockDispute); - - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - } - - /** - * @notice Calls the dispute module, sets the correct status of the dispute, emits events - */ - function test_disputeResponse() public { - // Add a response to the request - oracle.mock_addResponseId(_getId(mockRequest), _responseId); - - for (uint256 _i; _i < uint256(type(IOracle.DisputeStatus).max); _i++) { - // Set the new status - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_i)); - - // Mock and expect the disputeModule disputeResponse call - _mockAndExpect( - address(disputeModule), - abi.encodeCall(IDisputeModule.disputeResponse, (mockRequest, mockResponse, mockDispute)), - abi.encode(mockDispute) - ); - - // Check: emits ResponseDisputed event? - _expectEmit(address(oracle)); - emit ResponseDisputed(_responseId, _disputeId, mockDispute); - - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - - // Reset the dispute of the response - oracle.mock_setDisputeOf(_responseId, bytes32(0)); - } - } - - /** - * @notice Reverts if the dispute proposer and response proposer are not same - */ - function test_disputeResponse_revertIfProposerIsNotValid(address _otherProposer) public { - vm.assume(_otherProposer != proposer); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); - - mockDispute.proposer = _otherProposer; - - // Test: try to dispute the response - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Reverts if the response doesn't exist - */ - function test_disputeResponse_revertIfInvalidResponse() public { - oracle.mock_setResponseCreatedAt(_getId(mockResponse), 0); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); - - // Test: try to dispute the response - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Reverts if the caller and the disputer are not the same - */ - function test_disputeResponse_revertIfWrongDisputer(address _caller) public { - vm.assume(_caller != disputer); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidDisputer.selector); - - // Test: try to dispute the response again - vm.prank(_caller); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Reverts if the request has already been disputed - */ - function test_disputeResponse_revertIfAlreadyDisputed() public { - // Check: revert? - oracle.mock_setDisputeOf(_responseId, _disputeId); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); - - // Test: try to dispute the response again - vm.prank(disputer); - oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - } -} - -contract Oracle_Unit_UpdateDisputeStatus is BaseTest { - /** - * @notice Test if the dispute status is updated correctly and the event is emitted - * @dev This is testing every combination of previous and new status - */ - function test_updateDisputeStatus() public { - bytes32 _requestId = _getId(mockRequest); - oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); - - // Try every initial status - for (uint256 _previousStatus; _previousStatus < uint256(type(IOracle.DisputeStatus).max); _previousStatus++) { - // Try every new status - for (uint256 _newStatus; _newStatus < uint256(type(IOracle.DisputeStatus).max); _newStatus++) { - // Set the dispute status - mockDispute.requestId = _requestId; - bytes32 _disputeId = _getId(mockDispute); - - // Mock the dispute - oracle.mock_setDisputeOf(_getId(mockResponse), _getId(mockDispute)); - - // Mock and expect the disputeModule onDisputeStatusChange call - _mockAndExpect( - address(disputeModule), - abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), - abi.encode() - ); - - // Check: emits DisputeStatusUpdated event? - _expectEmit(address(oracle)); - emit DisputeStatusUpdated(_disputeId, mockDispute, IOracle.DisputeStatus(_newStatus)); - - // Test: change the status - vm.prank(address(resolutionModule)); - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); - - // Check: correct status stored? - assertEq(_newStatus, uint256(oracle.disputeStatus(_disputeId))); - } - } - } - - /** - * @notice Providing a dispute that does not match the response should revert - */ - function test_updateDisputeStatus_revertsIfInvalidDisputeId(bytes32 _randomId, uint256 _newStatus) public { - // 0 to 3 status, fuzzed - _newStatus = bound(_newStatus, 0, 3); - bytes32 _disputeId = _getId(mockDispute); - vm.assume(_randomId != _disputeId); - - // Setting a random dispute id, not matching the mockDispute - oracle.mock_setDisputeOf(_getId(mockResponse), _randomId); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); - - // Test: Try to update the dispute - vm.prank(proposer); - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); - } - - /** - * @notice If the sender is not the dispute/resolution module, the call should revert - */ - function test_updateDisputeStatus_revertsIfWrongCaller(uint256 _newStatus) public { - // 0 to 3 status, fuzzed - _newStatus = bound(_newStatus, 0, 3); - - bytes32 _disputeId = _getId(mockDispute); - bytes32 _responseId = _getId(mockResponse); - - // Mock the dispute - oracle.mock_setDisputeOf(_responseId, _disputeId); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NotDisputeOrResolutionModule.selector, proposer)); - - // Test: try to update the status from an EOA - vm.prank(proposer); - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); - } - - /** - * @notice If the dispute does not exist, the call should revert - */ - function test_updateDisputeStatus_revertsIfInvalidDispute() public { - bytes32 _disputeId = _getId(mockDispute); - - oracle.mock_setDisputeCreatedAt(_disputeId, 0); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); - - // Test: try to update the status - vm.prank(address(resolutionModule)); - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active); - } -} - -contract Oracle_Unit_ResolveDispute is BaseTest { - /** - * @notice Test if the resolution module is called and the event is emitted - */ - function test_resolveDispute_callsResolutionModule() public { - // Mock the dispute - bytes32 _disputeId = _getId(mockDispute); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - - oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); - - // Mock and expect the resolution module call - _mockAndExpect( - address(resolutionModule), - abi.encodeCall(IResolutionModule.resolveDispute, (_disputeId, mockRequest, mockResponse, mockDispute)), - abi.encode() - ); - - // Check: emits DisputeResolved event? - _expectEmit(address(oracle)); - emit DisputeResolved(_disputeId, mockDispute, address(this)); - - // Test: resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Test the revert when the function is called with an non-existent dispute id - */ - function test_resolveDispute_revertsIfInvalidDisputeId() public { - oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _getId(mockDispute))); - - // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Revert if the dispute doesn't exist - */ - function test_resolveDispute_revertsIfInvalidDispute() public { - oracle.mock_setDisputeCreatedAt(_getId(mockDispute), 0); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); - - // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Test the revert when the function is called with a dispute in unresolvable status - */ - function test_resolveDispute_revertsIfWrongDisputeStatus() public { - bytes32 _disputeId = _getId(mockDispute); - - for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { - if (_status == uint256(IOracle.DisputeStatus.Active) || _status == uint256(IOracle.DisputeStatus.Escalated)) { - continue; - } - - bytes32 _responseId = _getId(mockResponse); - - // Mock the dispute - oracle.mock_setDisputeOf(_responseId, _disputeId); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_status)); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotResolve.selector, _disputeId)); - - // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); - } - } - - /** - * @notice Revert if the request has no resolution module configured - */ - function test_resolveDispute_revertsIfNoResolutionModule() public { - // Clear the resolution module - mockRequest.resolutionModule = address(0); - bytes32 _requestId = _getId(mockRequest); - - mockResponse.requestId = _requestId; - bytes32 _responseId = _getId(mockResponse); - - mockDispute.requestId = _requestId; - mockDispute.responseId = _responseId; - bytes32 _disputeId = _getId(mockDispute); - - // Mock the dispute - oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Escalated); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - - // Check: revert? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NoResolutionModule.selector, _disputeId)); - - // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); - } -} - -contract Oracle_Unit_AllowedModule is BaseTest { - /** - * @notice Test if the modules are recognized as allowed and random addresses aren't - */ - function test_allowedModule(address _notAModule) public { - // Fuzz any address not in the modules of the request - vm.assume( - _notAModule != address(requestModule) && _notAModule != address(responseModule) - && _notAModule != address(disputeModule) && _notAModule != address(resolutionModule) - && _notAModule != address(finalityModule) - ); - - bytes32 _requestId = _getId(mockRequest); - oracle.mock_addAllowedModule(_requestId, address(requestModule)); - oracle.mock_addAllowedModule(_requestId, address(responseModule)); - oracle.mock_addAllowedModule(_requestId, address(disputeModule)); - oracle.mock_addAllowedModule(_requestId, address(resolutionModule)); - oracle.mock_addAllowedModule(_requestId, address(finalityModule)); - - // Check: the correct modules are recognized as valid - assertTrue(oracle.allowedModule(_requestId, address(requestModule))); - assertTrue(oracle.allowedModule(_requestId, address(responseModule))); - assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); - assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); - assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); - - // Check: any other address is not recognized as allowed module - assertFalse(oracle.allowedModule(_requestId, _notAModule)); - } -} - -contract Oracle_Unit_IsParticipant is BaseTest { - /** - * @notice Test if participants are recognized as such and random addresses aren't - */ - function test_isParticipant(bytes32 _requestId, address _notParticipant) public { - vm.assume(_notParticipant != requester && _notParticipant != proposer && _notParticipant != disputer); - - // Set valid participants - oracle.mock_addParticipant(_requestId, requester); - oracle.mock_addParticipant(_requestId, proposer); - oracle.mock_addParticipant(_requestId, disputer); - - // Check: the participants are recognized - assertTrue(oracle.isParticipant(_requestId, requester)); - assertTrue(oracle.isParticipant(_requestId, proposer)); - assertTrue(oracle.isParticipant(_requestId, disputer)); - - // Check: any other address is not recognized as a participant - assertFalse(oracle.isParticipant(_requestId, _notParticipant)); - } -} - -contract Oracle_Unit_Finalize is BaseTest { - modifier withoutResponse() { - mockResponse.requestId = bytes32(0); - _; - } - - /** - * @notice Finalizing with a valid response, the happy path - * @dev The request might or might not use a dispute and a finality module, this is fuzzed - */ - function test_finalize_withResponse( - bool _useResolutionAndFinality, - address _caller - ) public setResolutionAndFinality(_useResolutionAndFinality) { - bytes32 _requestId = _getId(mockRequest); - mockResponse.requestId = _requestId; - bytes32 _responseId = _getId(mockResponse); - - oracle.mock_addResponseId(_requestId, _responseId); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - - // Mock the finalize call on all modules - bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); - - _mockAndExpect(address(requestModule), _calldata, abi.encode()); - _mockAndExpect(address(responseModule), _calldata, abi.encode()); - _mockAndExpect(address(disputeModule), _calldata, abi.encode()); - - if (_useResolutionAndFinality) { - _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); - _mockAndExpect(address(finalityModule), _calldata, abi.encode()); - } - - // Check: emits OracleRequestFinalized event? - _expectEmit(address(oracle)); - emit OracleRequestFinalized(_requestId, _responseId, _caller); - - // Test: finalize the request - vm.prank(_caller); - oracle.finalize(mockRequest, mockResponse); - - assertEq(oracle.finalizedAt(_requestId), block.timestamp); - } - - /** - * @notice Revert if the request doesn't exist - */ - function test_finalize_revertsIfInvalidRequest() public { - oracle.mock_setRequestCreatedAt(_getId(mockRequest), 0); - vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); - - vm.prank(requester); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Revert if the response doesn't exist - */ - function test_finalize_revertsIfInvalidResponse() public { - bytes32 _requestId = _getId(mockRequest); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - oracle.mock_setResponseCreatedAt(_requestId, 0); - - // Check: revert? - vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); - - // Test: finalize the request - vm.prank(requester); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Finalizing an already finalized request - */ - function test_finalize_withResponse_revertsWhenAlreadyFinalized() public { - bytes32 _requestId = _getId(mockRequest); - bytes32 _responseId = _getId(mockResponse); - - // Test: finalize a finalized request - oracle.mock_setFinalizedAt(_requestId, block.timestamp); - oracle.mock_addResponseId(_requestId, _responseId); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); - vm.prank(requester); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Test the response validation, its requestId should match the id of the provided request - */ - function test_finalize_withResponse_revertsInvalidRequestId(bytes32 _requestId) public { - vm.assume(_requestId != bytes32(0) && _requestId != _getId(mockRequest)); - - mockResponse.requestId = _requestId; - bytes32 _responseId = _getId(mockResponse); - - // Store the response - oracle.mock_addResponseId(_requestId, _responseId); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_requestId, block.timestamp); - - // Test: finalize the request - vm.expectRevert(ValidatorLib.ValidatorLib_InvalidResponseBody.selector); - vm.prank(requester); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Finalizing a request with a successfully disputed response should revert - */ - function test_finalize_withResponse_revertsIfDisputedResponse(uint256 _status) public { - vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); - vm.assume(_status != uint256(IOracle.DisputeStatus.None)); - vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); - - bytes32 _requestId = _getId(mockRequest); - bytes32 _responseId = _getId(mockResponse); - bytes32 _disputeId = _getId(mockDispute); - - // Submit a response to the request - oracle.mock_addResponseId(_requestId, _responseId); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - oracle.mock_setDisputeOf(_responseId, _disputeId); - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Won); - - // Check: reverts? - vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); - - // Test: finalize the request - vm.prank(requester); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Finalizing with a blank response, meaning the request hasn't got any attention or the provided responses were invalid - * @dev The request might or might not use a dispute and a finality module, this is fuzzed - */ - function test_finalize_withoutResponse( - bool _useResolutionAndFinality, - address _caller - ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { - vm.assume(_caller != address(0)); - - bytes32 _requestId = _getId(mockRequest); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - mockResponse.requestId = bytes32(0); - - // Create mock request and store it - bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); - - _mockAndExpect(address(requestModule), _calldata, abi.encode()); - _mockAndExpect(address(responseModule), _calldata, abi.encode()); - _mockAndExpect(address(disputeModule), _calldata, abi.encode()); - - if (_useResolutionAndFinality) { - _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); - _mockAndExpect(address(finalityModule), _calldata, abi.encode()); - } - - // Check: emits OracleRequestFinalized event? - _expectEmit(address(oracle)); - emit OracleRequestFinalized(_requestId, bytes32(0), _caller); - - // Test: finalize the request - vm.prank(_caller); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Testing the finalization of a request with multiple responses all of which have been disputed - * @dev The request might or might not use a dispute and a finality module, this is fuzzed - */ - function test_finalize_withoutResponse_withMultipleDisputedResponses( - bool _useResolutionAndFinality, - address _caller, - uint8 _status, - uint8 _numberOfResponses - ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { - vm.assume(_numberOfResponses < 5); - - // All responses will have the same dispute status - vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); - vm.assume(_status != uint256(IOracle.DisputeStatus.None)); - vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); - - bytes32 _requestId = _getId(mockRequest); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - - IOracle.DisputeStatus _disputeStatus = IOracle.DisputeStatus(_status); - - for (uint8 _i; _i < _numberOfResponses; _i++) { - mockResponse.response = abi.encodePacked(_i); - - // Compute the mock object ids - bytes32 _responseId = _getId(mockResponse); - mockDispute.responseId = _responseId; - bytes32 _disputeId = _getId(mockDispute); - - // The response must be disputed - oracle.mock_addResponseId(_requestId, _responseId); - oracle.mock_setDisputeOf(_responseId, _disputeId); - oracle.mock_setDisputeStatus(_disputeId, _disputeStatus); - } - - mockResponse.response = bytes(''); - - // The finalization should come through - // Check: emits OracleRequestFinalized event? - _expectEmit(address(oracle)); - emit OracleRequestFinalized(_requestId, bytes32(0), _caller); - - // Test: finalize the request - vm.prank(_caller); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Finalizing an already finalized response shouldn't be possible - */ - function test_finalize_withoutResponse_revertsWhenAlreadyFinalized(address _caller) public withoutResponse { - bytes32 _requestId = _getId(mockRequest); - - // Override the finalizedAt to make it be finalized - oracle.mock_setFinalizedAt(_requestId, block.timestamp); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - - // Test: finalize a finalized request - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); - vm.prank(_caller); - oracle.finalize(mockRequest, mockResponse); - } - - /** - * @notice Finalizing a request with a non-disputed response should revert - */ - function test_finalize_withoutResponse_revertsWithNonDisputedResponse(bytes32 _responseId) public withoutResponse { - vm.assume(_responseId != bytes32(0)); - - bytes32 _requestId = _getId(mockRequest); - - // Submit a response to the request - oracle.mock_addResponseId(_requestId, _responseId); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - - // Check: reverts? - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_FinalizableResponseExists.selector, _responseId)); - - // Test: finalize the request - vm.prank(requester); - oracle.finalize(mockRequest, mockResponse); - } -} - -contract Oracle_Unit_EscalateDispute is BaseTest { - /** - * @notice Test if the dispute is escalated correctly and the event is emitted - */ - function test_escalateDispute() public { - bytes32 _disputeId = _getId(mockDispute); - - oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - - // Mock and expect the dispute module call - _mockAndExpect( - address(disputeModule), - abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), - abi.encode() - ); - - // Mock and expect the resolution module call - _mockAndExpect( - address(resolutionModule), - abi.encodeCall(IResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)), - abi.encode() - ); - - // Expect dispute escalated event - _expectEmit(address(oracle)); - emit DisputeEscalated(address(this), _disputeId, mockDispute); - - // Test: escalate the dispute - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - - // Check: The dispute has been escalated - assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); - } - - /** - * @notice Should not revert if no resolution module was configured - */ - function test_escalateDispute_noResolutionModule() public { - mockRequest.resolutionModule = address(0); - - bytes32 _requestId = _getId(mockRequest); - - mockResponse.requestId = _requestId; - bytes32 _responseId = _getId(mockResponse); - - mockDispute.requestId = _requestId; - mockDispute.responseId = _responseId; - bytes32 _disputeId = _getId(mockDispute); - - oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); - oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); - oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - - // Mock and expect the dispute module call - _mockAndExpect( - address(disputeModule), - abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), - abi.encode() - ); - - // Expect dispute escalated event - _expectEmit(address(oracle)); - emit DisputeEscalated(address(this), _disputeId, mockDispute); - - // Test: escalate the dispute - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - - // Check: The dispute has been escalated - assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); - } - - /** - * /** - * @notice Revert if the dispute doesn't exist - */ - function test_escalateDispute_revertsIfInvalidDispute() public { - bytes32 _disputeId = _getId(mockDispute); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, 0); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); - - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - } - - /** - * @notice Revert if the provided dispute does not match the request or the response - */ - function test_escalateDispute_revertsIfDisputeNotValid() public { - bytes32 _disputeId = _getId(mockDispute); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); - - // Test: escalate the dispute - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - } - - function test_escalateDispute_revertsIfDisputeNotActive() public { - bytes32 _disputeId = _getId(mockDispute); - oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); - oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); - - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); - - // Test: escalate the dispute - oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - } -} +// // SPDX-License-Identifier: MIT +// pragma solidity ^0.8.19; + +// import 'forge-std/Test.sol'; + +// import {IModule} from '../../interfaces/IModule.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 {Oracle} from '../../contracts/Oracle.sol'; +// import {Helpers} from '../utils/Helpers.sol'; + +// import {ValidatorLib} from '../../libraries/ValidatorLib.sol'; + +// /** +// * @notice Harness to deploy and test Oracle +// */ +// contract MockOracle is Oracle { +// constructor() Oracle() {} + +// function mock_addParticipant(bytes32 _requestId, address _participant) external { +// isParticipant[_requestId][_participant] = true; +// } + +// function mock_addAllowedModule(bytes32 _requestId, address _module) external { +// allowedModule[_requestId][_module] = true; +// } + +// function mock_setFinalizedResponseId(bytes32 _requestId, bytes32 _finalizedResponseId) external { +// finalizedResponseId[_requestId] = _finalizedResponseId; +// } + +// function mock_setFinalizedAt(bytes32 _requestId, uint256 _finalizedAt) external { +// finalizedAt[_requestId] = _finalizedAt; +// } + +// function mock_setDisputeOf(bytes32 _responseId, bytes32 _disputeId) external { +// disputeOf[_responseId] = _disputeId; +// } + +// function mock_setDisputeStatus(bytes32 _disputeId, IOracle.DisputeStatus _status) external { +// disputeStatus[_disputeId] = _status; +// } + +// function mock_setRequestId(uint256 _nonce, bytes32 _requestId) external { +// nonceToRequestId[_nonce] = _requestId; +// } + +// function mock_setRequestCreatedAt(bytes32 _requestId, uint256 _requestCreatedAt) external { +// requestCreatedAt[_requestId] = _requestCreatedAt; +// } + +// function mock_setResponseCreatedAt(bytes32 _responseId, uint256 _responseCreatedAt) external { +// responseCreatedAt[_responseId] = _responseCreatedAt; +// } + +// function mock_setDisputeCreatedAt(bytes32 _disputeId, uint256 _disputeCreatedAt) external { +// disputeCreatedAt[_disputeId] = _disputeCreatedAt; +// } + +// function mock_setTotalRequestCount(uint256 _totalRequestCount) external { +// totalRequestCount = _totalRequestCount; +// } + +// function mock_addResponseId(bytes32 _requestId, bytes32 _responseId) external { +// _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); +// } +// } + +// /** +// * @title Oracle Unit tests +// */ +// contract BaseTest is Test, Helpers { +// // The target contract +// MockOracle public 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')); + +// // Mock IPFS hash +// bytes32 internal _ipfsHash = bytes32('QmR4uiJH654k3Ta2uLLQ8r'); + +// // Events +// event RequestCreated(bytes32 indexed _requestId, IOracle.Request _request, bytes32 _ipfsHash); +// event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, IOracle.Response _response); +// event ResponseDisputed(bytes32 indexed _responseId, bytes32 indexed _disputeId, IOracle.Dispute _dispute); +// event OracleRequestFinalized(bytes32 indexed _requestId, bytes32 indexed _responseId, address indexed _caller); +// event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); +// event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); +// event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute, address indexed _caller); + +// function setUp() public virtual { +// oracle = new MockOracle(); + +// 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); +// } + +// /** +// * @notice If no dispute and finality module used, set them to address(0) +// */ +// modifier setResolutionAndFinality(bool _useResolutionAndFinality) { +// if (!_useResolutionAndFinality) { +// resolutionModule = IResolutionModule(address(0)); +// finalityModule = IFinalityModule(address(0)); +// } +// _; +// } +// } + +// contract Oracle_Unit_CreateRequest is BaseTest { +// /** +// * @notice Test the request creation with correct arguments and nonce increment +// * @dev The request might or might not use a dispute and a finality module, this is fuzzed +// */ +// function test_createRequest( +// bool _useResolutionAndFinality, +// bytes calldata _requestData, +// bytes calldata _responseData, +// bytes calldata _disputeData, +// bytes calldata _resolutionData, +// bytes calldata _finalityData +// ) public setResolutionAndFinality(_useResolutionAndFinality) { +// uint256 _initialNonce = oracle.totalRequestCount(); + +// // Create the request +// mockRequest.requestModuleData = _requestData; +// mockRequest.responseModuleData = _responseData; +// mockRequest.disputeModuleData = _disputeData; +// mockRequest.resolutionModuleData = _resolutionData; +// mockRequest.finalityModuleData = _finalityData; +// mockRequest.requester = requester; +// mockRequest.nonce = uint96(oracle.totalRequestCount()); + +// // Compute the associated request id +// bytes32 _theoreticalRequestId = _getId(mockRequest); + +// // Check: emits RequestCreated event? +// _expectEmit(address(oracle)); +// emit RequestCreated(_getId(mockRequest), mockRequest, _ipfsHash); + +// // Test: create the request +// vm.prank(requester); +// bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash); + +// // Check: Adds the requester to the list of participants +// assertTrue(oracle.isParticipant(_requestId, requester)); + +// // Check: Saves the number of the block +// assertEq(oracle.requestCreatedAt(_requestId), block.timestamp); + +// // Check: Sets allowedModules +// assertTrue(oracle.allowedModule(_requestId, address(requestModule))); +// assertTrue(oracle.allowedModule(_requestId, address(responseModule))); +// assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); + +// if (_useResolutionAndFinality) { +// assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); +// assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); +// } + +// // Check: Maps the nonce to the requestId +// assertEq(oracle.nonceToRequestId(mockRequest.nonce), _requestId); + +// // Check: correct request id returned? +// assertEq(_requestId, _theoreticalRequestId); + +// // Check: nonce incremented? +// assertEq(oracle.totalRequestCount(), _initialNonce + 1); +// } + +// /** +// * @notice Check that creating a request with a nonce that already exists reverts +// */ +// function test_createRequest_revertsIfInvalidNonce(uint256 _nonce) public { +// vm.assume(_nonce != oracle.totalRequestCount()); + +// // Set the nonce +// mockRequest.nonce = uint96(_nonce); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); + +// // Test: try to create the request +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); +// } + +// /** +// * @notice Check that creating a request with a misconfigured requester reverts +// */ +// function test_createRequest_revertsIfInvalidRequester(address _requester) public { +// vm.assume(_requester != requester); + +// // Set the nonce +// mockRequest.requester = _requester; + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); + +// // Test: try to create the request +// vm.prank(requester); +// oracle.createRequest(mockRequest, _ipfsHash); +// } +// } + +// contract Oracle_Unit_CreateRequests is BaseTest { +// /** +// * @notice Test creation of requests in batch mode. +// */ +// function test_createRequests( +// bytes calldata _requestData, +// bytes calldata _responseData, +// bytes calldata _disputeData +// ) public { +// uint256 _initialNonce = oracle.totalRequestCount(); +// uint256 _requestsAmount = 5; +// IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); +// bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); +// bool _useResolutionAndFinality = _requestData.length % 2 == 0; +// bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); + +// // Generate requests batch +// for (uint256 _i = 0; _i < _requestsAmount; _i++) { +// mockRequest.requestModuleData = _requestData; +// mockRequest.responseModuleData = _responseData; +// mockRequest.disputeModuleData = _disputeData; +// mockRequest.requester = requester; +// mockRequest.nonce = uint96(oracle.totalRequestCount() + _i); + +// bytes32 _theoreticalRequestId = _getId(mockRequest); +// _requests[_i] = mockRequest; +// _precalculatedIds[_i] = _theoreticalRequestId; +// _ipfsHashes[_i] = keccak256(abi.encode(_theoreticalRequestId, mockRequest.nonce)); + +// // Check: emits RequestCreated event? +// _expectEmit(address(oracle)); +// emit RequestCreated(_theoreticalRequestId, mockRequest, _ipfsHashes[_i]); +// } + +// vm.prank(requester); +// bytes32[] memory _requestsIds = oracle.createRequests(_requests, _ipfsHashes); + +// for (uint256 _i = 0; _i < _requestsIds.length; _i++) { +// assertEq(_requestsIds[_i], _precalculatedIds[_i]); + +// // Check: Adds the requester to the list of participants +// assertTrue(oracle.isParticipant(_requestsIds[_i], requester)); + +// // Check: Saves the number of the block +// assertEq(oracle.requestCreatedAt(_requestsIds[_i]), block.timestamp); + +// // Check: Sets allowedModules +// assertTrue(oracle.allowedModule(_requestsIds[_i], address(requestModule))); +// assertTrue(oracle.allowedModule(_requestsIds[_i], address(responseModule))); +// assertTrue(oracle.allowedModule(_requestsIds[_i], address(disputeModule))); + +// if (_useResolutionAndFinality) { +// assertTrue(oracle.allowedModule(_requestsIds[_i], address(resolutionModule))); +// assertTrue(oracle.allowedModule(_requestsIds[_i], address(finalityModule))); +// } + +// // Check: Maps the nonce to the requestId +// assertEq(oracle.nonceToRequestId(_requests[_i].nonce), _requestsIds[_i]); +// } + +// uint256 _newNonce = oracle.totalRequestCount(); +// assertEq(_newNonce, _initialNonce + _requestsAmount); +// } + +// /** +// * @notice Test creation of requests in batch mode with nonce 0. +// */ +// function test_createRequestsWithNonceZero( +// bytes calldata _requestData, +// bytes calldata _responseData, +// bytes calldata _disputeData +// ) public { +// uint256 _initialNonce = oracle.totalRequestCount(); +// uint256 _requestsAmount = 5; +// IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); +// bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); +// bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); + +// mockRequest.requestModuleData = _requestData; +// mockRequest.responseModuleData = _responseData; +// mockRequest.disputeModuleData = _disputeData; +// mockRequest.requester = requester; +// mockRequest.nonce = uint96(0); + +// bytes32 _theoreticalRequestId = _getId(mockRequest); +// bytes32 _ipfsHash = keccak256(abi.encode(_theoreticalRequestId, uint96(0))); + +// // Generate requests batch +// for (uint256 _i = 0; _i < _requestsAmount; _i++) { +// _requests[_i] = mockRequest; +// _precalculatedIds[_i] = _theoreticalRequestId; +// _ipfsHashes[_i] = _ipfsHash; +// } + +// vm.prank(requester); +// oracle.createRequests(_requests, _ipfsHashes); + +// uint256 _newNonce = oracle.totalRequestCount(); +// assertEq(_newNonce, _initialNonce + _requestsAmount); +// } +// } + +// contract Oracle_Unit_ListRequestIds is BaseTest { +// /** +// * @notice Test list requests ids, fuzz the batch size +// */ +// function test_listRequestIds(uint256 _numberOfRequests) public { +// // 0 to 10 request to list, fuzzed +// _numberOfRequests = bound(_numberOfRequests, 0, 10); + +// bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); + +// for (uint256 _i; _i < _numberOfRequests; _i++) { +// mockRequest.nonce = uint96(_i); +// bytes32 _requestId = _getId(mockRequest); +// _mockRequestIds[_i] = _requestId; +// oracle.mock_setRequestId(_i, _requestId); +// } + +// oracle.mock_setTotalRequestCount(_numberOfRequests); + +// // Test: fetching the requests +// bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); + +// // Check: enough request returned? +// assertEq(_requestsIds.length, _numberOfRequests); + +// // Check: correct requests returned (dummy are incremented)? +// for (uint256 _i; _i < _numberOfRequests; _i++) { +// assertEq(_requestsIds[_i], _mockRequestIds[_i]); +// } +// } + +// /** +// * @notice Test the request listing if asking for more request than it exists +// */ +// function test_listRequestIds_tooManyRequested(uint256 _numberOfRequests) public { +// // 1 to 10 request to list, fuzzed +// _numberOfRequests = bound(_numberOfRequests, 1, 10); + +// bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); + +// for (uint256 _i; _i < _numberOfRequests; _i++) { +// mockRequest.nonce = uint96(_i); +// bytes32 _requestId = _getId(mockRequest); +// _mockRequestIds[_i] = _requestId; +// oracle.mock_setRequestId(_i, _requestId); +// } + +// oracle.mock_setTotalRequestCount(_numberOfRequests); + +// // Test: fetching 1 extra request +// bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests + 1); + +// // Check: correct number of request returned? +// assertEq(_requestsIds.length, _numberOfRequests); + +// // Check: correct data? +// for (uint256 _i; _i < _numberOfRequests; _i++) { +// assertEq(_requestsIds[_i], _mockRequestIds[_i]); +// } + +// // Test: starting from an index outside of the range +// _requestsIds = oracle.listRequestIds(_numberOfRequests + 1, _numberOfRequests); +// assertEq(_requestsIds.length, 0); +// } + +// /** +// * @notice Test the request listing if there are no requests encoded +// */ +// function test_listRequestIds_zeroToReturn(uint256 _numberOfRequests) public { +// // Test: fetch any number of requests +// bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); + +// // Check; 0 returned? +// assertEq(_requestsIds.length, 0); +// } +// } + +// contract Oracle_Unit_ProposeResponse is BaseTest { +// /** +// * @notice Proposing a response should call the response module, emit an event and return the response id +// */ +// function test_proposeResponse(bytes calldata _responseData) public { +// bytes32 _requestId = _getId(mockRequest); + +// // Update mock response +// mockResponse.response = _responseData; + +// // Compute the response ID +// bytes32 _responseId = _getId(mockResponse); + +// // Set the request creation time +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + +// // Mock and expect the responseModule propose call: +// _mockAndExpect( +// address(responseModule), +// abi.encodeCall(IResponseModule.propose, (mockRequest, mockResponse, proposer)), +// abi.encode(mockResponse) +// ); + +// // Check: emits ResponseProposed event? +// _expectEmit(address(oracle)); +// emit ResponseProposed(_requestId, _responseId, mockResponse); + +// // Test: propose the response +// vm.prank(proposer); +// bytes32 _actualResponseId = oracle.proposeResponse(mockRequest, mockResponse); + +// mockResponse.response = bytes('secondResponse'); + +// // Check: emits ResponseProposed event? +// _expectEmit(address(oracle)); +// emit ResponseProposed(_requestId, _getId(mockResponse), mockResponse); + +// vm.prank(proposer); +// bytes32 _secondResponseId = oracle.proposeResponse(mockRequest, mockResponse); + +// // Check: correct response id returned? +// assertEq(_actualResponseId, _responseId); + +// // Check: responseId are unique? +// assertNotEq(_secondResponseId, _responseId); + +// // Check: correct response id stored in the id list and unique? +// bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); +// assertEq(_responseIds.length, 2); +// assertEq(_responseIds[0], _responseId); +// assertEq(_responseIds[1], _secondResponseId); +// } + +// function test_proposeResponse_revertsIfInvalidRequest() public { +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidRequest.selector); + +// // Test: try to propose a response with an invalid request +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); +// } + +// /** +// * @notice Revert if the caller is not the proposer nor the dispute module +// */ +// function test_proposeResponse_revertsIfInvalidCaller(address _caller) public { +// vm.assume(_caller != proposer && _caller != address(disputeModule)); + +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); + +// // Test: try to propose a response from a random address +// vm.prank(_caller); +// oracle.proposeResponse(mockRequest, mockResponse); +// } + +// /** +// * @notice Revert if the response has been already proposed +// */ +// function test_proposeResponse_revertsIfDuplicateResponse() public { +// // Set the request creation time +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + +// // Test: propose a response +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_ResponseAlreadyProposed.selector); + +// // Test: try to propose the same response again +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); +// } + +// /** +// * @notice Proposing a response to a finalized request should fail +// */ +// function test_proposeResponse_revertsIfAlreadyFinalized(uint128 _finalizedAt) public { +// vm.assume(_finalizedAt > 0); + +// // Set the finalization time +// bytes32 _requestId = _getId(mockRequest); +// oracle.mock_setFinalizedAt(_requestId, _finalizedAt); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + +// // Check: Reverts if already finalized? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, (_requestId))); +// vm.prank(proposer); +// oracle.proposeResponse(mockRequest, mockResponse); +// } +// } + +// contract Oracle_Unit_DisputeResponse is BaseTest { +// bytes32 internal _responseId; +// bytes32 internal _disputeId; + +// function setUp() public override { +// super.setUp(); + +// _responseId = _getId(mockResponse); +// _disputeId = _getId(mockDispute); + +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); +// } + +// /** +// * @notice Calls the dispute module, sets the correct status of the dispute, emits events +// */ +// function test_disputeResponse() public { +// // Add a response to the request +// oracle.mock_addResponseId(_getId(mockRequest), _responseId); + +// for (uint256 _i; _i < uint256(type(IOracle.DisputeStatus).max); _i++) { +// // Set the new status +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_i)); + +// // Mock and expect the disputeModule disputeResponse call +// _mockAndExpect( +// address(disputeModule), +// abi.encodeCall(IDisputeModule.disputeResponse, (mockRequest, mockResponse, mockDispute)), +// abi.encode(mockDispute) +// ); + +// // Check: emits ResponseDisputed event? +// _expectEmit(address(oracle)); +// emit ResponseDisputed(_responseId, _disputeId, mockDispute); + +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); + +// // Reset the dispute of the response +// oracle.mock_setDisputeOf(_responseId, bytes32(0)); +// } +// } + +// /** +// * @notice Reverts if the dispute proposer and response proposer are not same +// */ +// function test_disputeResponse_revertIfProposerIsNotValid(address _otherProposer) public { +// vm.assume(_otherProposer != proposer); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); + +// mockDispute.proposer = _otherProposer; + +// // Test: try to dispute the response +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Reverts if the response doesn't exist +// */ +// function test_disputeResponse_revertIfInvalidResponse() public { +// oracle.mock_setResponseCreatedAt(_getId(mockResponse), 0); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); + +// // Test: try to dispute the response +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Reverts if the caller and the disputer are not the same +// */ +// function test_disputeResponse_revertIfWrongDisputer(address _caller) public { +// vm.assume(_caller != disputer); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidDisputer.selector); + +// // Test: try to dispute the response again +// vm.prank(_caller); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Reverts if the request has already been disputed +// */ +// function test_disputeResponse_revertIfAlreadyDisputed() public { +// // Check: revert? +// oracle.mock_setDisputeOf(_responseId, _disputeId); +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); + +// // Test: try to dispute the response again +// vm.prank(disputer); +// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); +// } +// } + +// contract Oracle_Unit_UpdateDisputeStatus is BaseTest { +// /** +// * @notice Test if the dispute status is updated correctly and the event is emitted +// * @dev This is testing every combination of previous and new status +// */ +// function test_updateDisputeStatus() public { +// bytes32 _requestId = _getId(mockRequest); +// oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); + +// // Try every initial status +// for (uint256 _previousStatus; _previousStatus < uint256(type(IOracle.DisputeStatus).max); _previousStatus++) { +// // Try every new status +// for (uint256 _newStatus; _newStatus < uint256(type(IOracle.DisputeStatus).max); _newStatus++) { +// // Set the dispute status +// mockDispute.requestId = _requestId; +// bytes32 _disputeId = _getId(mockDispute); + +// // Mock the dispute +// oracle.mock_setDisputeOf(_getId(mockResponse), _getId(mockDispute)); + +// // Mock and expect the disputeModule onDisputeStatusChange call +// _mockAndExpect( +// address(disputeModule), +// abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), +// abi.encode() +// ); + +// // Check: emits DisputeStatusUpdated event? +// _expectEmit(address(oracle)); +// emit DisputeStatusUpdated(_disputeId, mockDispute, IOracle.DisputeStatus(_newStatus)); + +// // Test: change the status +// vm.prank(address(resolutionModule)); +// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); + +// // Check: correct status stored? +// assertEq(_newStatus, uint256(oracle.disputeStatus(_disputeId))); +// } +// } +// } + +// /** +// * @notice Providing a dispute that does not match the response should revert +// */ +// function test_updateDisputeStatus_revertsIfInvalidDisputeId(bytes32 _randomId, uint256 _newStatus) public { +// // 0 to 3 status, fuzzed +// _newStatus = bound(_newStatus, 0, 3); +// bytes32 _disputeId = _getId(mockDispute); +// vm.assume(_randomId != _disputeId); + +// // Setting a random dispute id, not matching the mockDispute +// oracle.mock_setDisputeOf(_getId(mockResponse), _randomId); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); + +// // Test: Try to update the dispute +// vm.prank(proposer); +// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); +// } + +// /** +// * @notice If the sender is not the dispute/resolution module, the call should revert +// */ +// function test_updateDisputeStatus_revertsIfWrongCaller(uint256 _newStatus) public { +// // 0 to 3 status, fuzzed +// _newStatus = bound(_newStatus, 0, 3); + +// bytes32 _disputeId = _getId(mockDispute); +// bytes32 _responseId = _getId(mockResponse); + +// // Mock the dispute +// oracle.mock_setDisputeOf(_responseId, _disputeId); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NotDisputeOrResolutionModule.selector, proposer)); + +// // Test: try to update the status from an EOA +// vm.prank(proposer); +// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); +// } + +// /** +// * @notice If the dispute does not exist, the call should revert +// */ +// function test_updateDisputeStatus_revertsIfInvalidDispute() public { +// bytes32 _disputeId = _getId(mockDispute); + +// oracle.mock_setDisputeCreatedAt(_disputeId, 0); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); + +// // Test: try to update the status +// vm.prank(address(resolutionModule)); +// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active); +// } +// } + +// contract Oracle_Unit_ResolveDispute is BaseTest { +// /** +// * @notice Test if the resolution module is called and the event is emitted +// */ +// function test_resolveDispute_callsResolutionModule() public { +// // Mock the dispute +// bytes32 _disputeId = _getId(mockDispute); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + +// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); + +// // Mock and expect the resolution module call +// _mockAndExpect( +// address(resolutionModule), +// abi.encodeCall(IResolutionModule.resolveDispute, (_disputeId, mockRequest, mockResponse, mockDispute)), +// abi.encode() +// ); + +// // Check: emits DisputeResolved event? +// _expectEmit(address(oracle)); +// emit DisputeResolved(_disputeId, mockDispute, address(this)); + +// // Test: resolve the dispute +// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Test the revert when the function is called with an non-existent dispute id +// */ +// function test_resolveDispute_revertsIfInvalidDisputeId() public { +// oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _getId(mockDispute))); + +// // Test: try to resolve the dispute +// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Revert if the dispute doesn't exist +// */ +// function test_resolveDispute_revertsIfInvalidDispute() public { +// oracle.mock_setDisputeCreatedAt(_getId(mockDispute), 0); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); + +// // Test: try to resolve the dispute +// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Test the revert when the function is called with a dispute in unresolvable status +// */ +// function test_resolveDispute_revertsIfWrongDisputeStatus() public { +// bytes32 _disputeId = _getId(mockDispute); + +// for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { +// if (_status == uint256(IOracle.DisputeStatus.Active) || _status == uint256(IOracle.DisputeStatus.Escalated)) { +// continue; +// } + +// bytes32 _responseId = _getId(mockResponse); + +// // Mock the dispute +// oracle.mock_setDisputeOf(_responseId, _disputeId); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_status)); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotResolve.selector, _disputeId)); + +// // Test: try to resolve the dispute +// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); +// } +// } + +// /** +// * @notice Revert if the request has no resolution module configured +// */ +// function test_resolveDispute_revertsIfNoResolutionModule() public { +// // Clear the resolution module +// mockRequest.resolutionModule = address(0); +// bytes32 _requestId = _getId(mockRequest); + +// mockResponse.requestId = _requestId; +// bytes32 _responseId = _getId(mockResponse); + +// mockDispute.requestId = _requestId; +// mockDispute.responseId = _responseId; +// bytes32 _disputeId = _getId(mockDispute); + +// // Mock the dispute +// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Escalated); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + +// // Check: revert? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NoResolutionModule.selector, _disputeId)); + +// // Test: try to resolve the dispute +// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); +// } +// } + +// contract Oracle_Unit_AllowedModule is BaseTest { +// /** +// * @notice Test if the modules are recognized as allowed and random addresses aren't +// */ +// function test_allowedModule(address _notAModule) public { +// // Fuzz any address not in the modules of the request +// vm.assume( +// _notAModule != address(requestModule) && _notAModule != address(responseModule) +// && _notAModule != address(disputeModule) && _notAModule != address(resolutionModule) +// && _notAModule != address(finalityModule) +// ); + +// bytes32 _requestId = _getId(mockRequest); +// oracle.mock_addAllowedModule(_requestId, address(requestModule)); +// oracle.mock_addAllowedModule(_requestId, address(responseModule)); +// oracle.mock_addAllowedModule(_requestId, address(disputeModule)); +// oracle.mock_addAllowedModule(_requestId, address(resolutionModule)); +// oracle.mock_addAllowedModule(_requestId, address(finalityModule)); + +// // Check: the correct modules are recognized as valid +// assertTrue(oracle.allowedModule(_requestId, address(requestModule))); +// assertTrue(oracle.allowedModule(_requestId, address(responseModule))); +// assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); +// assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); +// assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); + +// // Check: any other address is not recognized as allowed module +// assertFalse(oracle.allowedModule(_requestId, _notAModule)); +// } +// } + +// contract Oracle_Unit_IsParticipant is BaseTest { +// /** +// * @notice Test if participants are recognized as such and random addresses aren't +// */ +// function test_isParticipant(bytes32 _requestId, address _notParticipant) public { +// vm.assume(_notParticipant != requester && _notParticipant != proposer && _notParticipant != disputer); + +// // Set valid participants +// oracle.mock_addParticipant(_requestId, requester); +// oracle.mock_addParticipant(_requestId, proposer); +// oracle.mock_addParticipant(_requestId, disputer); + +// // Check: the participants are recognized +// assertTrue(oracle.isParticipant(_requestId, requester)); +// assertTrue(oracle.isParticipant(_requestId, proposer)); +// assertTrue(oracle.isParticipant(_requestId, disputer)); + +// // Check: any other address is not recognized as a participant +// assertFalse(oracle.isParticipant(_requestId, _notParticipant)); +// } +// } + +// contract Oracle_Unit_Finalize is BaseTest { +// modifier withoutResponse() { +// mockResponse.requestId = bytes32(0); +// _; +// } + +// /** +// * @notice Finalizing with a valid response, the happy path +// * @dev The request might or might not use a dispute and a finality module, this is fuzzed +// */ +// function test_finalize_withResponse( +// bool _useResolutionAndFinality, +// address _caller +// ) public setResolutionAndFinality(_useResolutionAndFinality) { +// bytes32 _requestId = _getId(mockRequest); +// mockResponse.requestId = _requestId; +// bytes32 _responseId = _getId(mockResponse); + +// oracle.mock_addResponseId(_requestId, _responseId); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + +// // Mock the finalize call on all modules +// bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); + +// _mockAndExpect(address(requestModule), _calldata, abi.encode()); +// _mockAndExpect(address(responseModule), _calldata, abi.encode()); +// _mockAndExpect(address(disputeModule), _calldata, abi.encode()); + +// if (_useResolutionAndFinality) { +// _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); +// _mockAndExpect(address(finalityModule), _calldata, abi.encode()); +// } + +// // Check: emits OracleRequestFinalized event? +// _expectEmit(address(oracle)); +// emit OracleRequestFinalized(_requestId, _responseId, _caller); + +// // Test: finalize the request +// vm.prank(_caller); +// oracle.finalize(mockRequest, mockResponse); + +// assertEq(oracle.finalizedAt(_requestId), block.timestamp); +// } + +// /** +// * @notice Revert if the request doesn't exist +// */ +// function test_finalize_revertsIfInvalidRequest() public { +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), 0); +// vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); + +// vm.prank(requester); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Revert if the response doesn't exist +// */ +// function test_finalize_revertsIfInvalidResponse() public { +// bytes32 _requestId = _getId(mockRequest); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// oracle.mock_setResponseCreatedAt(_requestId, 0); + +// // Check: revert? +// vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); + +// // Test: finalize the request +// vm.prank(requester); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Finalizing an already finalized request +// */ +// function test_finalize_withResponse_revertsWhenAlreadyFinalized() public { +// bytes32 _requestId = _getId(mockRequest); +// bytes32 _responseId = _getId(mockResponse); + +// // Test: finalize a finalized request +// oracle.mock_setFinalizedAt(_requestId, block.timestamp); +// oracle.mock_addResponseId(_requestId, _responseId); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); +// vm.prank(requester); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Test the response validation, its requestId should match the id of the provided request +// */ +// function test_finalize_withResponse_revertsInvalidRequestId(bytes32 _requestId) public { +// vm.assume(_requestId != bytes32(0) && _requestId != _getId(mockRequest)); + +// mockResponse.requestId = _requestId; +// bytes32 _responseId = _getId(mockResponse); + +// // Store the response +// oracle.mock_addResponseId(_requestId, _responseId); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_requestId, block.timestamp); + +// // Test: finalize the request +// vm.expectRevert(ValidatorLib.ValidatorLib_InvalidResponseBody.selector); +// vm.prank(requester); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Finalizing a request with a successfully disputed response should revert +// */ +// function test_finalize_withResponse_revertsIfDisputedResponse(uint256 _status) public { +// vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); +// vm.assume(_status != uint256(IOracle.DisputeStatus.None)); +// vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); + +// bytes32 _requestId = _getId(mockRequest); +// bytes32 _responseId = _getId(mockResponse); +// bytes32 _disputeId = _getId(mockDispute); + +// // Submit a response to the request +// oracle.mock_addResponseId(_requestId, _responseId); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); +// oracle.mock_setDisputeOf(_responseId, _disputeId); +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Won); + +// // Check: reverts? +// vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); + +// // Test: finalize the request +// vm.prank(requester); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Finalizing with a blank response, meaning the request hasn't got any attention or the provided responses were invalid +// * @dev The request might or might not use a dispute and a finality module, this is fuzzed +// */ +// function test_finalize_withoutResponse( +// bool _useResolutionAndFinality, +// address _caller +// ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { +// vm.assume(_caller != address(0)); + +// bytes32 _requestId = _getId(mockRequest); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// mockResponse.requestId = bytes32(0); + +// // Create mock request and store it +// bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); + +// _mockAndExpect(address(requestModule), _calldata, abi.encode()); +// _mockAndExpect(address(responseModule), _calldata, abi.encode()); +// _mockAndExpect(address(disputeModule), _calldata, abi.encode()); + +// if (_useResolutionAndFinality) { +// _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); +// _mockAndExpect(address(finalityModule), _calldata, abi.encode()); +// } + +// // Check: emits OracleRequestFinalized event? +// _expectEmit(address(oracle)); +// emit OracleRequestFinalized(_requestId, bytes32(0), _caller); + +// // Test: finalize the request +// vm.prank(_caller); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Testing the finalization of a request with multiple responses all of which have been disputed +// * @dev The request might or might not use a dispute and a finality module, this is fuzzed +// */ +// function test_finalize_withoutResponse_withMultipleDisputedResponses( +// bool _useResolutionAndFinality, +// address _caller, +// uint8 _status, +// uint8 _numberOfResponses +// ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { +// vm.assume(_numberOfResponses < 5); + +// // All responses will have the same dispute status +// vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); +// vm.assume(_status != uint256(IOracle.DisputeStatus.None)); +// vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); + +// bytes32 _requestId = _getId(mockRequest); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + +// IOracle.DisputeStatus _disputeStatus = IOracle.DisputeStatus(_status); + +// for (uint8 _i; _i < _numberOfResponses; _i++) { +// mockResponse.response = abi.encodePacked(_i); + +// // Compute the mock object ids +// bytes32 _responseId = _getId(mockResponse); +// mockDispute.responseId = _responseId; +// bytes32 _disputeId = _getId(mockDispute); + +// // The response must be disputed +// oracle.mock_addResponseId(_requestId, _responseId); +// oracle.mock_setDisputeOf(_responseId, _disputeId); +// oracle.mock_setDisputeStatus(_disputeId, _disputeStatus); +// } + +// mockResponse.response = bytes(''); + +// // The finalization should come through +// // Check: emits OracleRequestFinalized event? +// _expectEmit(address(oracle)); +// emit OracleRequestFinalized(_requestId, bytes32(0), _caller); + +// // Test: finalize the request +// vm.prank(_caller); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Finalizing an already finalized response shouldn't be possible +// */ +// function test_finalize_withoutResponse_revertsWhenAlreadyFinalized(address _caller) public withoutResponse { +// bytes32 _requestId = _getId(mockRequest); + +// // Override the finalizedAt to make it be finalized +// oracle.mock_setFinalizedAt(_requestId, block.timestamp); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + +// // Test: finalize a finalized request +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); +// vm.prank(_caller); +// oracle.finalize(mockRequest, mockResponse); +// } + +// /** +// * @notice Finalizing a request with a non-disputed response should revert +// */ +// function test_finalize_withoutResponse_revertsWithNonDisputedResponse(bytes32 _responseId) public withoutResponse { +// vm.assume(_responseId != bytes32(0)); + +// bytes32 _requestId = _getId(mockRequest); + +// // Submit a response to the request +// oracle.mock_addResponseId(_requestId, _responseId); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + +// // Check: reverts? +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_FinalizableResponseExists.selector, _responseId)); + +// // Test: finalize the request +// vm.prank(requester); +// oracle.finalize(mockRequest, mockResponse); +// } +// } + +// contract Oracle_Unit_EscalateDispute is BaseTest { +// /** +// * @notice Test if the dispute is escalated correctly and the event is emitted +// */ +// function test_escalateDispute() public { +// bytes32 _disputeId = _getId(mockDispute); + +// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + +// // Mock and expect the dispute module call +// _mockAndExpect( +// address(disputeModule), +// abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), +// abi.encode() +// ); + +// // Mock and expect the resolution module call +// _mockAndExpect( +// address(resolutionModule), +// abi.encodeCall(IResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)), +// abi.encode() +// ); + +// // Expect dispute escalated event +// _expectEmit(address(oracle)); +// emit DisputeEscalated(address(this), _disputeId, mockDispute); + +// // Test: escalate the dispute +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); + +// // Check: The dispute has been escalated +// assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); +// } + +// /** +// * @notice Should not revert if no resolution module was configured +// */ +// function test_escalateDispute_noResolutionModule() public { +// mockRequest.resolutionModule = address(0); + +// bytes32 _requestId = _getId(mockRequest); + +// mockResponse.requestId = _requestId; +// bytes32 _responseId = _getId(mockResponse); + +// mockDispute.requestId = _requestId; +// mockDispute.responseId = _responseId; +// bytes32 _disputeId = _getId(mockDispute); + +// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); +// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); +// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); +// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + +// // Mock and expect the dispute module call +// _mockAndExpect( +// address(disputeModule), +// abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), +// abi.encode() +// ); + +// // Expect dispute escalated event +// _expectEmit(address(oracle)); +// emit DisputeEscalated(address(this), _disputeId, mockDispute); + +// // Test: escalate the dispute +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); + +// // Check: The dispute has been escalated +// assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); +// } + +// /** +// * /** +// * @notice Revert if the dispute doesn't exist +// */ +// function test_escalateDispute_revertsIfInvalidDispute() public { +// bytes32 _disputeId = _getId(mockDispute); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, 0); +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); + +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); +// } + +// /** +// * @notice Revert if the provided dispute does not match the request or the response +// */ +// function test_escalateDispute_revertsIfDisputeNotValid() public { +// bytes32 _disputeId = _getId(mockDispute); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); + +// // Test: escalate the dispute +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); +// } + +// function test_escalateDispute_revertsIfDisputeNotActive() public { +// bytes32 _disputeId = _getId(mockDispute); +// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); +// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); +// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); +// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); + +// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); + +// // Test: escalate the dispute +// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); +// } +// } From 2a2a4f519964f2e01be8b17c5fa208e6b2a051c8 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Thu, 17 Oct 2024 15:27:25 -0300 Subject: [PATCH 02/19] feat: access control --- solidity/contracts/Oracle.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index d78f4dd..32b405e 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -450,6 +450,7 @@ contract Oracle is IOracle, AccessController { allowedModule[_requestId][_request.disputeModule] = true; allowedModule[_requestId][_request.resolutionModule] = true; allowedModule[_requestId][_request.finalityModule] = true; + allowedModule[_requestId][_request.accessControlModule] = true; isParticipant[_requestId][msg.sender] = true; From bfebad14337380aea90e2e62b06f136efb21e238 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Wed, 23 Oct 2024 23:00:25 -0300 Subject: [PATCH 03/19] fix: comments --- solidity/contracts/Oracle.sol | 1 - solidity/contracts/utils/OracleTypehash.sol | 14 ++++++-------- solidity/interfaces/IOracle.sol | 2 -- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 32b405e..d78f4dd 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -450,7 +450,6 @@ contract Oracle is IOracle, AccessController { allowedModule[_requestId][_request.disputeModule] = true; allowedModule[_requestId][_request.resolutionModule] = true; allowedModule[_requestId][_request.finalityModule] = true; - allowedModule[_requestId][_request.accessControlModule] = true; isParticipant[_requestId][msg.sender] = true; diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index 5a7e951..f6d3838 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -1,17 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -bytes32 constant _PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request, Response _response)'); +bytes32 constant _PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request,Response _response)'); -bytes32 constant _DISPUTE_TYPEHASH = - keccak256('DisputeResponse(Request _request, Response _response, Dispute _dispute)'); +bytes32 constant _DISPUTE_TYPEHASH = keccak256('DisputeResponse(Request _request,Response _response,Dispute _dispute)'); -bytes32 constant _ESCALATE_TYPEHASH = - keccak256('EscalateDispute(Request _request, Response _response, Dispute _dispute)'); +bytes32 constant _ESCALATE_TYPEHASH = keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); -bytes32 constant _RESOLVE_TYPEHASH = keccak256('ResolveDispute(Request _request, Response _response, Dispute _dispute)'); +bytes32 constant _RESOLVE_TYPEHASH = keccak256('ResolveDispute(Request _request,Response _response,Dispute _dispute)'); bytes32 constant _UPDATE_TYPEHASH = - keccak256('UpdateDisputeStatus(Request _request, Response _response, Dispute _dispute, DisputeStatus _status)'); + keccak256('UpdateDisputeStatus(Request _request,Response _response,Dispute _dispute,DisputeStatus _status)'); -bytes32 constant _FINALIZE_TYPEHASH = keccak256('Finalize(Request _request, Response _response)'); +bytes32 constant _FINALIZE_TYPEHASH = keccak256('Finalize(Request _request,Response _response)'); diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index cfd8837..df8effb 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -202,7 +202,6 @@ interface IOracle is IAccessController { * @param disputeModuleData The parameters for the dispute module * @param resolutionModuleData The parameters for the resolution module * @param finalityModuleData The parameters for the finality module - * @param accessControlModuleData The parameters for the access control module * @param requester The address of the user who created the request * @param nonce The nonce of the request */ @@ -220,7 +219,6 @@ interface IOracle is IAccessController { bytes disputeModuleData; bytes resolutionModuleData; bytes finalityModuleData; - bytes accessControlModuleData; } /** From 7fd9bbfbe44dbca6d2109ba961ce9d1501ba7524 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Thu, 24 Oct 2024 01:50:13 -0300 Subject: [PATCH 04/19] feat: tests --- solidity/contracts/AccessController.sol | 2 +- solidity/contracts/Oracle.sol | 31 +- solidity/contracts/utils/OracleTypehash.sol | 3 + solidity/interfaces/IAccessController.sol | 2 +- solidity/interfaces/IOracle.sol | 11 +- .../test/integration/EscalateDispute.t.sol | 65 +- solidity/test/integration/Finalization.t.sol | 285 +- solidity/test/integration/IntegrationBase.sol | 15 + .../test/integration/ResponseDispute.t.sol | 67 +- .../test/integration/ResponseProposal.t.sol | 68 +- .../contracts/MockAccessControlModule.sol | 31 + .../interfaces/IMockAccessControlModule.sol | 6 + solidity/test/unit/Oracle.t.sol | 2659 +++++++++-------- solidity/test/utils/Helpers.sol | 3 + 14 files changed, 1748 insertions(+), 1500 deletions(-) create mode 100644 solidity/test/mocks/contracts/MockAccessControlModule.sol create mode 100644 solidity/test/mocks/interfaces/IMockAccessControlModule.sol diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol index 344e513..100a03a 100644 --- a/solidity/contracts/AccessController.sol +++ b/solidity/contracts/AccessController.sol @@ -27,7 +27,7 @@ abstract contract AccessController is IAccessController { _data: _accessControl.data }) ); - if (!_hasAccess) revert IAccessControlData_NoAccess(); + if (!_hasAccess) revert AccessControlData_NoAccess(); _; } } diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index d78f4dd..dbc19a5 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -13,6 +13,7 @@ import {ValidatorLib} from '../libraries/ValidatorLib.sol'; import {AccessController} from './AccessController.sol'; import { + _CREATE_TYPEHASH, _DISPUTE_TYPEHASH, _ESCALATE_TYPEHASH, _FINALIZE_TYPEHASH, @@ -107,20 +108,25 @@ contract Oracle is IOracle, AccessController { } /// @inheritdoc IOracle - function createRequest(Request calldata _request, bytes32 _ipfsHash) external returns (bytes32 _requestId) { - _requestId = _createRequest(_request, _ipfsHash); + function createRequest( + Request calldata _request, + bytes32 _ipfsHash, + AccessControl calldata _accessControl + ) external returns (bytes32 _requestId) { + _requestId = _createRequest(_request, _ipfsHash, _accessControl); } /// @inheritdoc IOracle function createRequests( Request[] calldata _requestsData, - bytes32[] calldata _ipfsHashes + bytes32[] calldata _ipfsHashes, + AccessControl[] calldata _accessControl ) external returns (bytes32[] memory _batchRequestsIds) { uint256 _requestsAmount = _requestsData.length; _batchRequestsIds = new bytes32[](_requestsAmount); for (uint256 _i = 0; _i < _requestsAmount;) { - _batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i]); + _batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i], _accessControl[_i]); unchecked { ++_i; } @@ -430,14 +436,23 @@ contract Oracle is IOracle, AccessController { * * @param _request The request to be created * @param _ipfsHash The hashed IPFS CID of the metadata json + * @param _accessControl The access control struct * @return _requestId The id of the created request */ - function _createRequest(Request memory _request, bytes32 _ipfsHash) internal returns (bytes32 _requestId) { + function _createRequest( + Request memory _request, + bytes32 _ipfsHash, + AccessControl calldata _accessControl + ) + internal + hasAccess(_request.accessControlModule, _CREATE_TYPEHASH, abi.encode(_request), _accessControl) + returns (bytes32 _requestId) + { uint256 _requestNonce = totalRequestCount++; if (_request.nonce == 0) _request.nonce = uint96(_requestNonce); - if (msg.sender != _request.requester || _requestNonce != _request.nonce) { + if (_accessControl.user != _request.requester || _requestNonce != _request.nonce) { revert Oracle_InvalidRequestBody(); } @@ -451,9 +466,9 @@ contract Oracle is IOracle, AccessController { allowedModule[_requestId][_request.resolutionModule] = true; allowedModule[_requestId][_request.finalityModule] = true; - isParticipant[_requestId][msg.sender] = true; + isParticipant[_requestId][_accessControl.user] = true; - IRequestModule(_request.requestModule).createRequest(_requestId, _request.requestModuleData, msg.sender); + IRequestModule(_request.requestModule).createRequest(_requestId, _request.requestModuleData, _accessControl.user); emit RequestCreated(_requestId, _request, _ipfsHash); } diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index f6d3838..2a7029e 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -1,6 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +bytes32 constant _CREATE_TYPEHASH = + keccak256('CreateRequest(Request _request,bytes32 _ipfsHash,AccessControl _accessControl'); + bytes32 constant _PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request,Response _response)'); bytes32 constant _DISPUTE_TYPEHASH = keccak256('DisputeResponse(Request _request,Response _response,Dispute _dispute)'); diff --git a/solidity/interfaces/IAccessController.sol b/solidity/interfaces/IAccessController.sol index 0857e24..9bd6854 100644 --- a/solidity/interfaces/IAccessController.sol +++ b/solidity/interfaces/IAccessController.sol @@ -25,5 +25,5 @@ interface IAccessController { /** * @notice Reverts if the caller has no access */ - error IAccessControlData_NoAccess(); + error AccessControlData_NoAccess(); } diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index df8effb..0f4672f 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -376,9 +376,14 @@ interface IOracle is IAccessController { * @dev The modules must be real contracts following the IModule interface * @param _request The request data * @param _ipfsHash The hashed IPFS CID of the metadata json + * @param _accessControl The access control data * @return _requestId The id of the request, can be used to propose a response or query results */ - function createRequest(Request memory _request, bytes32 _ipfsHash) external returns (bytes32 _requestId); + function createRequest( + Request memory _request, + bytes32 _ipfsHash, + AccessControl calldata _accessControl + ) external returns (bytes32 _requestId); /** * @notice Creates multiple requests, the same way as createRequest @@ -386,10 +391,12 @@ interface IOracle is IAccessController { * @param _requestsData The array of calldata for each request * @return _batchRequestsIds The array of request IDs * @param _ipfsHashes The array of hashed IPFS CIDs of the metadata files + * @param _accessControl The array of access control datas */ function createRequests( Request[] calldata _requestsData, - bytes32[] calldata _ipfsHashes + bytes32[] calldata _ipfsHashes, + AccessControl[] calldata _accessControl ) external returns (bytes32[] memory _batchRequestsIds); /** diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index 31a50be..83d317f 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -1,31 +1,40 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -// import './IntegrationBase.sol'; - -// contract Integration_EscalateDispute is IntegrationBase { -// function test_escalateDispute() public { -// // Create the request -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); - -// // Submit a response -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); - -// // Dispute the response -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - -// // We escalate the dispute -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - -// // We check that the dispute was escalated -// bytes32 _disputeId = _getId(mockDispute); -// assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.Escalated); - -// // Escalate dispute reverts if dispute is not active -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); -// } -// } +import './IntegrationBase.sol'; + +contract Integration_EscalateDispute is IntegrationBase { + function test_escalateDispute() public { + // Create the request + mockAccessControl.user = requester; + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + // Submit a response + mockAccessControl.user = proposer; + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + vm.stopPrank(); + + // Dispute reverts if caller is not authorized + vm.startPrank(badCaller); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + mockAccessControl.user = disputer; + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + vm.stopPrank(); + + // Dispute the response + vm.startPrank(caller); + mockAccessControl.user = disputer; + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // We escalate the dispute + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // We check that the dispute was escalated + bytes32 _disputeId = _getId(mockDispute); + assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.Escalated); + + // Escalate dispute reverts if dispute is not active + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } +} diff --git a/solidity/test/integration/Finalization.t.sol b/solidity/test/integration/Finalization.t.sol index ca35c5a..09a0156 100644 --- a/solidity/test/integration/Finalization.t.sol +++ b/solidity/test/integration/Finalization.t.sol @@ -1,139 +1,152 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -// import './IntegrationBase.sol'; - -// contract Integration_Finalization is IntegrationBase { -// address internal _finalizer = makeAddr('finalizer'); -// address internal _callbackTarget = makeAddr('target'); - -// function setUp() public override { -// super.setUp(); -// vm.etch(_callbackTarget, hex'069420'); -// } - -// /** -// * @notice Test to check if another module can be set as callback module. -// */ -// function test_targetIsAnotherModule() public { -// mockRequest.finalityModuleData = abi.encode( -// IMockFinalityModule.RequestParameters({ -// target: address(_finalityModule), -// data: abi.encodeWithSignature('callback()') -// }) -// ); - -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); - -// _jumpToFinalization(); - -// vm.warp(block.timestamp + _baseDisputeWindow); -// vm.prank(_finalizer); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Test to check that finalization data is set and callback calls are made. -// */ -// function test_makeAndIgnoreLowLevelCalls(bytes memory _calldata) public { -// mockRequest.finalityModuleData = -// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); - -// vm.prank(requester); -// bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash); - -// _jumpToFinalization(); - -// // Check: all low-level calls are made? -// vm.expectCall(_callbackTarget, _calldata); - -// vm.warp(block.timestamp + _baseDisputeWindow); -// vm.prank(_finalizer); -// oracle.finalize(mockRequest, mockResponse); - -// bytes32 _responseId = oracle.finalizedResponseId(_requestId); -// // Check: is request finalized? -// assertEq(_responseId, _getId(mockResponse)); -// } - -// /** -// * @notice Test to check that finalizing a request that has no response will succeed. -// */ -// function test_finalizeWithoutResponse() public { -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); - -// mockResponse.requestId = bytes32(0); - -// // Check: finalizes if request has no response? -// vm.prank(_finalizer); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Test to check that finalizing a request with a ongoing dispute will revert. -// */ -// function test_revertFinalizeWithDisputedResponse() public { -// mockRequest.finalityModuleData = -// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); - -// mockResponse.requestId = _getId(mockRequest); -// mockDispute.requestId = mockResponse.requestId; -// mockDispute.responseId = _getId(mockResponse); - -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); - -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); - -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - -// vm.prank(_finalizer); -// vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Test to check that finalizing a request with a ongoing dispute will revert. -// */ -// function test_revertFinalizeInDisputeWindow() public { -// mockRequest.finalityModuleData = -// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); - -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); -// } - -// /** -// * @notice Test to check that finalizing a request without disputes triggers callback calls and executes without reverting. -// */ -// function test_finalizeWithUndisputedResponse(bytes calldata _calldata) public { -// mockRequest.finalityModuleData = -// abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); - -// vm.expectCall(_callbackTarget, _calldata); -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); - -// _jumpToFinalization(); - -// vm.warp(block.timestamp + _baseDisputeWindow); -// vm.prank(_finalizer); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Internal helper function to setup the finalization stage of a request. -// */ -// function _jumpToFinalization() internal returns (bytes32 _responseId) { -// mockResponse.requestId = _getId(mockRequest); - -// vm.prank(proposer); -// _responseId = oracle.proposeResponse(mockRequest, mockResponse); - -// vm.warp(_expectedDeadline + 1); -// } -// } +import './IntegrationBase.sol'; + +contract Integration_Finalization is IntegrationBase { + address internal _callbackTarget = makeAddr('target'); + + function setUp() public override { + super.setUp(); + vm.etch(_callbackTarget, hex'069420'); + } + + /** + * @notice Test to check if another module can be set as callback module. + */ + function test_targetIsAnotherModule() public { + mockRequest.finalityModuleData = abi.encode( + IMockFinalityModule.RequestParameters({ + target: address(_finalityModule), + data: abi.encodeWithSignature('callback()') + }) + ); + + mockAccessControl.user = requester; + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + _jumpToFinalization(); + + vm.warp(block.timestamp + _baseDisputeWindow); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Test to check that finalization data is set and callback calls are made. + */ + function test_makeAndIgnoreLowLevelCalls(bytes memory _calldata) public { + mockRequest.finalityModuleData = + abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); + + mockAccessControl.user = requester; + bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + _jumpToFinalization(); + + // Check: all low-level calls are made? + vm.expectCall(_callbackTarget, _calldata); + + vm.warp(block.timestamp + _baseDisputeWindow); + mockAccessControl.user = finalizer; + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + + bytes32 _responseId = oracle.finalizedResponseId(_requestId); + // Check: is request finalized? + assertEq(_responseId, _getId(mockResponse)); + } + + /** + * @notice Test to check that finalizing a request that has no response will succeed. + */ + function test_finalizeWithoutResponse() public { + mockAccessControl.user = requester; + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + vm.stopPrank(); + + mockResponse.requestId = bytes32(0); + mockAccessControl.user = finalizer; + + vm.startPrank(badCaller); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + vm.stopPrank(); + + // Check: finalizes if request has no response? + vm.prank(caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Test to check that finalizing a request with a ongoing dispute will revert. + */ + function test_revertFinalizeWithDisputedResponse() public { + mockRequest.finalityModuleData = + abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); + + mockResponse.requestId = _getId(mockRequest); + mockDispute.requestId = mockResponse.requestId; + mockDispute.responseId = _getId(mockResponse); + + mockAccessControl.user = requester; + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + mockAccessControl.user = proposer; + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + mockAccessControl.user = disputer; + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + + mockAccessControl.user = finalizer; + vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Test to check that finalizing a request with a ongoing dispute will revert. + */ + function test_revertFinalizeInDisputeWindow() public { + mockRequest.finalityModuleData = + abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: bytes('')})); + + mockAccessControl.user = requester; + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + } + + /** + * @notice Test to check that finalizing a request without disputes triggers callback calls and executes without reverting. + */ + function test_finalizeWithUndisputedResponse(bytes calldata _calldata) public { + mockRequest.finalityModuleData = + abi.encode(IMockFinalityModule.RequestParameters({target: _callbackTarget, data: _calldata})); + + mockAccessControl.user = requester; + vm.expectCall(_callbackTarget, _calldata); + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + _jumpToFinalization(); + + vm.warp(block.timestamp + _baseDisputeWindow); + mockAccessControl.user = finalizer; + vm.stopPrank(); + + vm.startPrank(badCaller); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + vm.stopPrank(); + + vm.prank(caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Internal helper function to setup the finalization stage of a request. + */ + function _jumpToFinalization() internal returns (bytes32 _responseId) { + mockResponse.requestId = _getId(mockRequest); + + mockAccessControl.user = proposer; + _responseId = oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + vm.warp(_expectedDeadline + 1); + } +} diff --git a/solidity/test/integration/IntegrationBase.sol b/solidity/test/integration/IntegrationBase.sol index e3810d8..f15ccfe 100644 --- a/solidity/test/integration/IntegrationBase.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -7,18 +7,21 @@ import {console} from 'forge-std/console.sol'; import {IDisputeModule} from '../../interfaces/modules/dispute/IDisputeModule.sol'; +import {IAccessControlModule} from '../../interfaces/modules/accessControl/IAccessControlModule.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 {IOracle, Oracle} from '../../contracts/Oracle.sol'; +import {IAccessController} from '../../interfaces/IAccessController.sol'; import {IMockAccounting, MockAccounting} from '../mocks/contracts/MockAccounting.sol'; import {MockCallback} from '../mocks/contracts/MockCallback.sol'; import {IMockDisputeModule, MockDisputeModule} from '../mocks/contracts/MockDisputeModule.sol'; +import {IMockAccessControlModule, MockAccessControlModule} from '../mocks/contracts/MockAccessControlModule.sol'; import {IMockFinalityModule, MockFinalityModule} from '../mocks/contracts/MockFinalityModule.sol'; import {IMockRequestModule, MockRequestModule} from '../mocks/contracts/MockRequestModule.sol'; import {IMockResolutionModule, MockResolutionModule} from '../mocks/contracts/MockResolutionModule.sol'; @@ -38,6 +41,8 @@ contract IntegrationBase is TestConstants, Helpers { address public keeper = makeAddr('keeper'); address public governance = makeAddr('governance'); + address public caller = makeAddr('caller'); + address public badCaller = makeAddr('badCaller'); Oracle public oracle; MockAccounting internal _accountingExtension; @@ -46,6 +51,7 @@ contract IntegrationBase is TestConstants, Helpers { MockDisputeModule internal _disputeModule; MockResolutionModule internal _resolutionModule; MockFinalityModule internal _finalityModule; + MockAccessControlModule internal _accessControlModule; MockCallback internal _mockCallback; IERC20 public usdc = IERC20(_label(USDC_ADDRESS, 'USDC')); @@ -86,6 +92,7 @@ contract IntegrationBase is TestConstants, Helpers { _disputeModule = new MockDisputeModule(oracle); _resolutionModule = new MockResolutionModule(oracle); _finalityModule = new MockFinalityModule(oracle); + _accessControlModule = new MockAccessControlModule(oracle); vm.stopPrank(); @@ -131,6 +138,7 @@ contract IntegrationBase is TestConstants, Helpers { mockRequest.disputeModule = address(_disputeModule); mockRequest.resolutionModule = address(_resolutionModule); mockRequest.finalityModule = address(_finalityModule); + mockRequest.accessControlModule = address(_accessControlModule); mockRequest.requester = requester; // Configure the mock response @@ -139,6 +147,13 @@ contract IntegrationBase is TestConstants, Helpers { // Configure the mock dispute mockDispute.requestId = _getId(mockRequest); mockDispute.responseId = _getId(mockResponse); + + // Add the allowed callers to the access control module + address[] memory _allowedCallers = new address[](1); + _allowedCallers[0] = caller; + _accessControlModule.setHasAccess(_allowedCallers); + + vm.startPrank(caller); } function _mineBlock() internal { diff --git a/solidity/test/integration/ResponseDispute.t.sol b/solidity/test/integration/ResponseDispute.t.sol index df81ff5..3b815d7 100644 --- a/solidity/test/integration/ResponseDispute.t.sol +++ b/solidity/test/integration/ResponseDispute.t.sol @@ -1,48 +1,49 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -// import './IntegrationBase.sol'; +import './IntegrationBase.sol'; -// contract Integration_ResponseDispute is IntegrationBase { -// bytes internal _responseData; -// bytes32 internal _requestId; -// bytes32 internal _responseId; +contract Integration_ResponseDispute is IntegrationBase { + bytes internal _responseData; + bytes32 internal _requestId; + bytes32 internal _responseId; -// function setUp() public override { -// super.setUp(); + function setUp() public override { + super.setUp(); -// _expectedDeadline = block.timestamp + BLOCK_TIME * 600; -// _responseData = abi.encode('response'); + _expectedDeadline = block.timestamp + BLOCK_TIME * 600; + _responseData = abi.encode('response'); -// mockRequest.nonce = uint96(oracle.totalRequestCount()); + mockRequest.nonce = uint96(oracle.totalRequestCount()); -// vm.prank(requester); -// _requestId = oracle.createRequest(mockRequest, _ipfsHash); + mockAccessControl.user = requester; + _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); -// mockResponse.requestId = _requestId; + mockResponse.requestId = _requestId; -// vm.prank(proposer); -// _responseId = oracle.proposeResponse(mockRequest, mockResponse); -// } + mockAccessControl.user = proposer; + _responseId = oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } -// function test_disputeResponse_alreadyFinalized() public { -// vm.warp(_expectedDeadline + _baseDisputeWindow); -// oracle.finalize(mockRequest, mockResponse); + function test_disputeResponse_alreadyFinalized() public { + vm.warp(_expectedDeadline + _baseDisputeWindow); + mockAccessControl.user = finalizer; + oracle.finalize(mockRequest, mockResponse, mockAccessControl); -// vm.prank(disputer); -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); -// } + mockAccessControl.user = disputer; + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } -// function test_disputeResponse_alreadyDisputed() public { -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); + function test_disputeResponse_alreadyDisputed() public { + mockAccessControl.user = disputer; + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); -// address _anotherDisputer = makeAddr('anotherDisputer'); -// mockDispute.disputer = _anotherDisputer; + address _anotherDisputer = makeAddr('anotherDisputer'); + mockDispute.disputer = _anotherDisputer; -// vm.prank(_anotherDisputer); -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); -// } -// } + mockAccessControl.user = _anotherDisputer; + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } +} diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index 9b3675a..0467de9 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -1,45 +1,53 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -// import './IntegrationBase.sol'; +import './IntegrationBase.sol'; -// contract Integration_ResponseProposal is IntegrationBase { -// bytes32 internal _requestId; +contract Integration_ResponseProposal is IntegrationBase { + bytes32 internal _requestId; -// function setUp() public override { -// super.setUp(); + function setUp() public override { + super.setUp(); + vm.stopPrank(); -// mockRequest.nonce = uint96(oracle.totalRequestCount()); + mockRequest.nonce = uint96(oracle.totalRequestCount()); + mockAccessControl.user = requester; -// vm.prank(requester); -// _requestId = oracle.createRequest(mockRequest, _ipfsHash); -// } + vm.startPrank(badCaller); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + vm.stopPrank(); -// function test_proposeResponse_validResponse(bytes memory _response) public { -// mockResponse.response = _response; + vm.startPrank(caller); + _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + } -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); + function test_proposeResponse_validResponse(bytes memory _response) public { + mockResponse.response = _response; -// bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); + mockAccessControl.user = proposer; + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); -// // Check: response data is correctly stored? -// assertEq(_responseIds.length, 1); -// assertEq(_responseIds[0], _getId(mockResponse)); -// } + bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); -// function test_proposeResponse_finalizedRequest(uint256 _timestamp) public { -// _timestamp = bound(_timestamp, _expectedDeadline + _baseDisputeWindow, type(uint128).max); + // Check: response data is correctly stored? + assertEq(_responseIds.length, 1); + assertEq(_responseIds[0], _getId(mockResponse)); + } -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); + function test_proposeResponse_finalizedRequest(uint256 _timestamp) public { + _timestamp = bound(_timestamp, _expectedDeadline + _baseDisputeWindow, type(uint128).max); -// vm.warp(_timestamp); -// oracle.finalize(mockRequest, mockResponse); + mockAccessControl.user = proposer; + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); -// mockResponse.response = abi.encode(_timestamp); -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); -// } -// } + vm.warp(_timestamp); + mockAccessControl.user = finalizer; + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + + mockAccessControl.user = proposer; + mockResponse.response = abi.encode(_timestamp); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } +} diff --git a/solidity/test/mocks/contracts/MockAccessControlModule.sol b/solidity/test/mocks/contracts/MockAccessControlModule.sol new file mode 100644 index 0000000..5069614 --- /dev/null +++ b/solidity/test/mocks/contracts/MockAccessControlModule.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {Module} from '../../../contracts/Module.sol'; + +import {IOracle} from '../../../interfaces/IOracle.sol'; +import {IMockAccessControlModule} from '../interfaces/IMockAccessControlModule.sol'; + +contract MockAccessControlModule is Module, IMockAccessControlModule { + mapping(address _caller => bool _hasAccess) public callerHasAccess; + + constructor(IOracle _oracle) Module(_oracle) {} + + function setHasAccess(address[] calldata _caller) external { + for (uint256 _i; _i < _caller.length; _i++) { + callerHasAccess[_caller[_i]] = true; + } + } + + function moduleName() external view returns (string memory _moduleName) {} + + function hasAccess( + address _caller, + address, + bytes32, + bytes memory, + bytes memory + ) external view override returns (bool) { + return callerHasAccess[_caller]; + } +} diff --git a/solidity/test/mocks/interfaces/IMockAccessControlModule.sol b/solidity/test/mocks/interfaces/IMockAccessControlModule.sol new file mode 100644 index 0000000..bd4ee2d --- /dev/null +++ b/solidity/test/mocks/interfaces/IMockAccessControlModule.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IAccessControlModule} from '../../../interfaces/modules/accessControl/IAccessControlModule.sol'; + +interface IMockAccessControlModule is IAccessControlModule {} diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 91dc30e..2d37deb 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -1,1261 +1,1398 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// import 'forge-std/Test.sol'; - -// import {IModule} from '../../interfaces/IModule.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 {Oracle} from '../../contracts/Oracle.sol'; -// import {Helpers} from '../utils/Helpers.sol'; - -// import {ValidatorLib} from '../../libraries/ValidatorLib.sol'; - -// /** -// * @notice Harness to deploy and test Oracle -// */ -// contract MockOracle is Oracle { -// constructor() Oracle() {} - -// function mock_addParticipant(bytes32 _requestId, address _participant) external { -// isParticipant[_requestId][_participant] = true; -// } - -// function mock_addAllowedModule(bytes32 _requestId, address _module) external { -// allowedModule[_requestId][_module] = true; -// } - -// function mock_setFinalizedResponseId(bytes32 _requestId, bytes32 _finalizedResponseId) external { -// finalizedResponseId[_requestId] = _finalizedResponseId; -// } - -// function mock_setFinalizedAt(bytes32 _requestId, uint256 _finalizedAt) external { -// finalizedAt[_requestId] = _finalizedAt; -// } - -// function mock_setDisputeOf(bytes32 _responseId, bytes32 _disputeId) external { -// disputeOf[_responseId] = _disputeId; -// } - -// function mock_setDisputeStatus(bytes32 _disputeId, IOracle.DisputeStatus _status) external { -// disputeStatus[_disputeId] = _status; -// } - -// function mock_setRequestId(uint256 _nonce, bytes32 _requestId) external { -// nonceToRequestId[_nonce] = _requestId; -// } - -// function mock_setRequestCreatedAt(bytes32 _requestId, uint256 _requestCreatedAt) external { -// requestCreatedAt[_requestId] = _requestCreatedAt; -// } - -// function mock_setResponseCreatedAt(bytes32 _responseId, uint256 _responseCreatedAt) external { -// responseCreatedAt[_responseId] = _responseCreatedAt; -// } - -// function mock_setDisputeCreatedAt(bytes32 _disputeId, uint256 _disputeCreatedAt) external { -// disputeCreatedAt[_disputeId] = _disputeCreatedAt; -// } - -// function mock_setTotalRequestCount(uint256 _totalRequestCount) external { -// totalRequestCount = _totalRequestCount; -// } - -// function mock_addResponseId(bytes32 _requestId, bytes32 _responseId) external { -// _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); -// } -// } - -// /** -// * @title Oracle Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// MockOracle public 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')); - -// // Mock IPFS hash -// bytes32 internal _ipfsHash = bytes32('QmR4uiJH654k3Ta2uLLQ8r'); - -// // Events -// event RequestCreated(bytes32 indexed _requestId, IOracle.Request _request, bytes32 _ipfsHash); -// event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, IOracle.Response _response); -// event ResponseDisputed(bytes32 indexed _responseId, bytes32 indexed _disputeId, IOracle.Dispute _dispute); -// event OracleRequestFinalized(bytes32 indexed _requestId, bytes32 indexed _responseId, address indexed _caller); -// event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); -// event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); -// event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute, address indexed _caller); - -// function setUp() public virtual { -// oracle = new MockOracle(); - -// 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); -// } - -// /** -// * @notice If no dispute and finality module used, set them to address(0) -// */ -// modifier setResolutionAndFinality(bool _useResolutionAndFinality) { -// if (!_useResolutionAndFinality) { -// resolutionModule = IResolutionModule(address(0)); -// finalityModule = IFinalityModule(address(0)); -// } -// _; -// } -// } - -// contract Oracle_Unit_CreateRequest is BaseTest { -// /** -// * @notice Test the request creation with correct arguments and nonce increment -// * @dev The request might or might not use a dispute and a finality module, this is fuzzed -// */ -// function test_createRequest( -// bool _useResolutionAndFinality, -// bytes calldata _requestData, -// bytes calldata _responseData, -// bytes calldata _disputeData, -// bytes calldata _resolutionData, -// bytes calldata _finalityData -// ) public setResolutionAndFinality(_useResolutionAndFinality) { -// uint256 _initialNonce = oracle.totalRequestCount(); - -// // Create the request -// mockRequest.requestModuleData = _requestData; -// mockRequest.responseModuleData = _responseData; -// mockRequest.disputeModuleData = _disputeData; -// mockRequest.resolutionModuleData = _resolutionData; -// mockRequest.finalityModuleData = _finalityData; -// mockRequest.requester = requester; -// mockRequest.nonce = uint96(oracle.totalRequestCount()); - -// // Compute the associated request id -// bytes32 _theoreticalRequestId = _getId(mockRequest); - -// // Check: emits RequestCreated event? -// _expectEmit(address(oracle)); -// emit RequestCreated(_getId(mockRequest), mockRequest, _ipfsHash); - -// // Test: create the request -// vm.prank(requester); -// bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash); - -// // Check: Adds the requester to the list of participants -// assertTrue(oracle.isParticipant(_requestId, requester)); - -// // Check: Saves the number of the block -// assertEq(oracle.requestCreatedAt(_requestId), block.timestamp); - -// // Check: Sets allowedModules -// assertTrue(oracle.allowedModule(_requestId, address(requestModule))); -// assertTrue(oracle.allowedModule(_requestId, address(responseModule))); -// assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); - -// if (_useResolutionAndFinality) { -// assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); -// assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); -// } - -// // Check: Maps the nonce to the requestId -// assertEq(oracle.nonceToRequestId(mockRequest.nonce), _requestId); - -// // Check: correct request id returned? -// assertEq(_requestId, _theoreticalRequestId); - -// // Check: nonce incremented? -// assertEq(oracle.totalRequestCount(), _initialNonce + 1); -// } - -// /** -// * @notice Check that creating a request with a nonce that already exists reverts -// */ -// function test_createRequest_revertsIfInvalidNonce(uint256 _nonce) public { -// vm.assume(_nonce != oracle.totalRequestCount()); - -// // Set the nonce -// mockRequest.nonce = uint96(_nonce); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); - -// // Test: try to create the request -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); -// } - -// /** -// * @notice Check that creating a request with a misconfigured requester reverts -// */ -// function test_createRequest_revertsIfInvalidRequester(address _requester) public { -// vm.assume(_requester != requester); - -// // Set the nonce -// mockRequest.requester = _requester; - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); - -// // Test: try to create the request -// vm.prank(requester); -// oracle.createRequest(mockRequest, _ipfsHash); -// } -// } - -// contract Oracle_Unit_CreateRequests is BaseTest { -// /** -// * @notice Test creation of requests in batch mode. -// */ -// function test_createRequests( -// bytes calldata _requestData, -// bytes calldata _responseData, -// bytes calldata _disputeData -// ) public { -// uint256 _initialNonce = oracle.totalRequestCount(); -// uint256 _requestsAmount = 5; -// IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); -// bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); -// bool _useResolutionAndFinality = _requestData.length % 2 == 0; -// bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); - -// // Generate requests batch -// for (uint256 _i = 0; _i < _requestsAmount; _i++) { -// mockRequest.requestModuleData = _requestData; -// mockRequest.responseModuleData = _responseData; -// mockRequest.disputeModuleData = _disputeData; -// mockRequest.requester = requester; -// mockRequest.nonce = uint96(oracle.totalRequestCount() + _i); - -// bytes32 _theoreticalRequestId = _getId(mockRequest); -// _requests[_i] = mockRequest; -// _precalculatedIds[_i] = _theoreticalRequestId; -// _ipfsHashes[_i] = keccak256(abi.encode(_theoreticalRequestId, mockRequest.nonce)); - -// // Check: emits RequestCreated event? -// _expectEmit(address(oracle)); -// emit RequestCreated(_theoreticalRequestId, mockRequest, _ipfsHashes[_i]); -// } - -// vm.prank(requester); -// bytes32[] memory _requestsIds = oracle.createRequests(_requests, _ipfsHashes); - -// for (uint256 _i = 0; _i < _requestsIds.length; _i++) { -// assertEq(_requestsIds[_i], _precalculatedIds[_i]); - -// // Check: Adds the requester to the list of participants -// assertTrue(oracle.isParticipant(_requestsIds[_i], requester)); - -// // Check: Saves the number of the block -// assertEq(oracle.requestCreatedAt(_requestsIds[_i]), block.timestamp); - -// // Check: Sets allowedModules -// assertTrue(oracle.allowedModule(_requestsIds[_i], address(requestModule))); -// assertTrue(oracle.allowedModule(_requestsIds[_i], address(responseModule))); -// assertTrue(oracle.allowedModule(_requestsIds[_i], address(disputeModule))); - -// if (_useResolutionAndFinality) { -// assertTrue(oracle.allowedModule(_requestsIds[_i], address(resolutionModule))); -// assertTrue(oracle.allowedModule(_requestsIds[_i], address(finalityModule))); -// } - -// // Check: Maps the nonce to the requestId -// assertEq(oracle.nonceToRequestId(_requests[_i].nonce), _requestsIds[_i]); -// } - -// uint256 _newNonce = oracle.totalRequestCount(); -// assertEq(_newNonce, _initialNonce + _requestsAmount); -// } - -// /** -// * @notice Test creation of requests in batch mode with nonce 0. -// */ -// function test_createRequestsWithNonceZero( -// bytes calldata _requestData, -// bytes calldata _responseData, -// bytes calldata _disputeData -// ) public { -// uint256 _initialNonce = oracle.totalRequestCount(); -// uint256 _requestsAmount = 5; -// IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); -// bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); -// bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); - -// mockRequest.requestModuleData = _requestData; -// mockRequest.responseModuleData = _responseData; -// mockRequest.disputeModuleData = _disputeData; -// mockRequest.requester = requester; -// mockRequest.nonce = uint96(0); - -// bytes32 _theoreticalRequestId = _getId(mockRequest); -// bytes32 _ipfsHash = keccak256(abi.encode(_theoreticalRequestId, uint96(0))); - -// // Generate requests batch -// for (uint256 _i = 0; _i < _requestsAmount; _i++) { -// _requests[_i] = mockRequest; -// _precalculatedIds[_i] = _theoreticalRequestId; -// _ipfsHashes[_i] = _ipfsHash; -// } - -// vm.prank(requester); -// oracle.createRequests(_requests, _ipfsHashes); - -// uint256 _newNonce = oracle.totalRequestCount(); -// assertEq(_newNonce, _initialNonce + _requestsAmount); -// } -// } - -// contract Oracle_Unit_ListRequestIds is BaseTest { -// /** -// * @notice Test list requests ids, fuzz the batch size -// */ -// function test_listRequestIds(uint256 _numberOfRequests) public { -// // 0 to 10 request to list, fuzzed -// _numberOfRequests = bound(_numberOfRequests, 0, 10); - -// bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); - -// for (uint256 _i; _i < _numberOfRequests; _i++) { -// mockRequest.nonce = uint96(_i); -// bytes32 _requestId = _getId(mockRequest); -// _mockRequestIds[_i] = _requestId; -// oracle.mock_setRequestId(_i, _requestId); -// } - -// oracle.mock_setTotalRequestCount(_numberOfRequests); - -// // Test: fetching the requests -// bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); - -// // Check: enough request returned? -// assertEq(_requestsIds.length, _numberOfRequests); - -// // Check: correct requests returned (dummy are incremented)? -// for (uint256 _i; _i < _numberOfRequests; _i++) { -// assertEq(_requestsIds[_i], _mockRequestIds[_i]); -// } -// } - -// /** -// * @notice Test the request listing if asking for more request than it exists -// */ -// function test_listRequestIds_tooManyRequested(uint256 _numberOfRequests) public { -// // 1 to 10 request to list, fuzzed -// _numberOfRequests = bound(_numberOfRequests, 1, 10); - -// bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); - -// for (uint256 _i; _i < _numberOfRequests; _i++) { -// mockRequest.nonce = uint96(_i); -// bytes32 _requestId = _getId(mockRequest); -// _mockRequestIds[_i] = _requestId; -// oracle.mock_setRequestId(_i, _requestId); -// } - -// oracle.mock_setTotalRequestCount(_numberOfRequests); - -// // Test: fetching 1 extra request -// bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests + 1); - -// // Check: correct number of request returned? -// assertEq(_requestsIds.length, _numberOfRequests); - -// // Check: correct data? -// for (uint256 _i; _i < _numberOfRequests; _i++) { -// assertEq(_requestsIds[_i], _mockRequestIds[_i]); -// } - -// // Test: starting from an index outside of the range -// _requestsIds = oracle.listRequestIds(_numberOfRequests + 1, _numberOfRequests); -// assertEq(_requestsIds.length, 0); -// } - -// /** -// * @notice Test the request listing if there are no requests encoded -// */ -// function test_listRequestIds_zeroToReturn(uint256 _numberOfRequests) public { -// // Test: fetch any number of requests -// bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); - -// // Check; 0 returned? -// assertEq(_requestsIds.length, 0); -// } -// } - -// contract Oracle_Unit_ProposeResponse is BaseTest { -// /** -// * @notice Proposing a response should call the response module, emit an event and return the response id -// */ -// function test_proposeResponse(bytes calldata _responseData) public { -// bytes32 _requestId = _getId(mockRequest); - -// // Update mock response -// mockResponse.response = _responseData; - -// // Compute the response ID -// bytes32 _responseId = _getId(mockResponse); - -// // Set the request creation time -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - -// // Mock and expect the responseModule propose call: -// _mockAndExpect( -// address(responseModule), -// abi.encodeCall(IResponseModule.propose, (mockRequest, mockResponse, proposer)), -// abi.encode(mockResponse) -// ); - -// // Check: emits ResponseProposed event? -// _expectEmit(address(oracle)); -// emit ResponseProposed(_requestId, _responseId, mockResponse); - -// // Test: propose the response -// vm.prank(proposer); -// bytes32 _actualResponseId = oracle.proposeResponse(mockRequest, mockResponse); - -// mockResponse.response = bytes('secondResponse'); - -// // Check: emits ResponseProposed event? -// _expectEmit(address(oracle)); -// emit ResponseProposed(_requestId, _getId(mockResponse), mockResponse); - -// vm.prank(proposer); -// bytes32 _secondResponseId = oracle.proposeResponse(mockRequest, mockResponse); - -// // Check: correct response id returned? -// assertEq(_actualResponseId, _responseId); - -// // Check: responseId are unique? -// assertNotEq(_secondResponseId, _responseId); - -// // Check: correct response id stored in the id list and unique? -// bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); -// assertEq(_responseIds.length, 2); -// assertEq(_responseIds[0], _responseId); -// assertEq(_responseIds[1], _secondResponseId); -// } - -// function test_proposeResponse_revertsIfInvalidRequest() public { -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidRequest.selector); - -// // Test: try to propose a response with an invalid request -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); -// } - -// /** -// * @notice Revert if the caller is not the proposer nor the dispute module -// */ -// function test_proposeResponse_revertsIfInvalidCaller(address _caller) public { -// vm.assume(_caller != proposer && _caller != address(disputeModule)); - -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); - -// // Test: try to propose a response from a random address -// vm.prank(_caller); -// oracle.proposeResponse(mockRequest, mockResponse); -// } - -// /** -// * @notice Revert if the response has been already proposed -// */ -// function test_proposeResponse_revertsIfDuplicateResponse() public { -// // Set the request creation time -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - -// // Test: propose a response -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_ResponseAlreadyProposed.selector); - -// // Test: try to propose the same response again -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); -// } - -// /** -// * @notice Proposing a response to a finalized request should fail -// */ -// function test_proposeResponse_revertsIfAlreadyFinalized(uint128 _finalizedAt) public { -// vm.assume(_finalizedAt > 0); - -// // Set the finalization time -// bytes32 _requestId = _getId(mockRequest); -// oracle.mock_setFinalizedAt(_requestId, _finalizedAt); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - -// // Check: Reverts if already finalized? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, (_requestId))); -// vm.prank(proposer); -// oracle.proposeResponse(mockRequest, mockResponse); -// } -// } - -// contract Oracle_Unit_DisputeResponse is BaseTest { -// bytes32 internal _responseId; -// bytes32 internal _disputeId; - -// function setUp() public override { -// super.setUp(); - -// _responseId = _getId(mockResponse); -// _disputeId = _getId(mockDispute); - -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); -// } - -// /** -// * @notice Calls the dispute module, sets the correct status of the dispute, emits events -// */ -// function test_disputeResponse() public { -// // Add a response to the request -// oracle.mock_addResponseId(_getId(mockRequest), _responseId); - -// for (uint256 _i; _i < uint256(type(IOracle.DisputeStatus).max); _i++) { -// // Set the new status -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_i)); - -// // Mock and expect the disputeModule disputeResponse call -// _mockAndExpect( -// address(disputeModule), -// abi.encodeCall(IDisputeModule.disputeResponse, (mockRequest, mockResponse, mockDispute)), -// abi.encode(mockDispute) -// ); - -// // Check: emits ResponseDisputed event? -// _expectEmit(address(oracle)); -// emit ResponseDisputed(_responseId, _disputeId, mockDispute); - -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); - -// // Reset the dispute of the response -// oracle.mock_setDisputeOf(_responseId, bytes32(0)); -// } -// } - -// /** -// * @notice Reverts if the dispute proposer and response proposer are not same -// */ -// function test_disputeResponse_revertIfProposerIsNotValid(address _otherProposer) public { -// vm.assume(_otherProposer != proposer); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); - -// mockDispute.proposer = _otherProposer; - -// // Test: try to dispute the response -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Reverts if the response doesn't exist -// */ -// function test_disputeResponse_revertIfInvalidResponse() public { -// oracle.mock_setResponseCreatedAt(_getId(mockResponse), 0); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); - -// // Test: try to dispute the response -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Reverts if the caller and the disputer are not the same -// */ -// function test_disputeResponse_revertIfWrongDisputer(address _caller) public { -// vm.assume(_caller != disputer); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidDisputer.selector); - -// // Test: try to dispute the response again -// vm.prank(_caller); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Reverts if the request has already been disputed -// */ -// function test_disputeResponse_revertIfAlreadyDisputed() public { -// // Check: revert? -// oracle.mock_setDisputeOf(_responseId, _disputeId); -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); - -// // Test: try to dispute the response again -// vm.prank(disputer); -// oracle.disputeResponse(mockRequest, mockResponse, mockDispute); -// } -// } - -// contract Oracle_Unit_UpdateDisputeStatus is BaseTest { -// /** -// * @notice Test if the dispute status is updated correctly and the event is emitted -// * @dev This is testing every combination of previous and new status -// */ -// function test_updateDisputeStatus() public { -// bytes32 _requestId = _getId(mockRequest); -// oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); - -// // Try every initial status -// for (uint256 _previousStatus; _previousStatus < uint256(type(IOracle.DisputeStatus).max); _previousStatus++) { -// // Try every new status -// for (uint256 _newStatus; _newStatus < uint256(type(IOracle.DisputeStatus).max); _newStatus++) { -// // Set the dispute status -// mockDispute.requestId = _requestId; -// bytes32 _disputeId = _getId(mockDispute); - -// // Mock the dispute -// oracle.mock_setDisputeOf(_getId(mockResponse), _getId(mockDispute)); - -// // Mock and expect the disputeModule onDisputeStatusChange call -// _mockAndExpect( -// address(disputeModule), -// abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), -// abi.encode() -// ); - -// // Check: emits DisputeStatusUpdated event? -// _expectEmit(address(oracle)); -// emit DisputeStatusUpdated(_disputeId, mockDispute, IOracle.DisputeStatus(_newStatus)); - -// // Test: change the status -// vm.prank(address(resolutionModule)); -// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); - -// // Check: correct status stored? -// assertEq(_newStatus, uint256(oracle.disputeStatus(_disputeId))); -// } -// } -// } - -// /** -// * @notice Providing a dispute that does not match the response should revert -// */ -// function test_updateDisputeStatus_revertsIfInvalidDisputeId(bytes32 _randomId, uint256 _newStatus) public { -// // 0 to 3 status, fuzzed -// _newStatus = bound(_newStatus, 0, 3); -// bytes32 _disputeId = _getId(mockDispute); -// vm.assume(_randomId != _disputeId); - -// // Setting a random dispute id, not matching the mockDispute -// oracle.mock_setDisputeOf(_getId(mockResponse), _randomId); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); - -// // Test: Try to update the dispute -// vm.prank(proposer); -// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); -// } - -// /** -// * @notice If the sender is not the dispute/resolution module, the call should revert -// */ -// function test_updateDisputeStatus_revertsIfWrongCaller(uint256 _newStatus) public { -// // 0 to 3 status, fuzzed -// _newStatus = bound(_newStatus, 0, 3); - -// bytes32 _disputeId = _getId(mockDispute); -// bytes32 _responseId = _getId(mockResponse); - -// // Mock the dispute -// oracle.mock_setDisputeOf(_responseId, _disputeId); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NotDisputeOrResolutionModule.selector, proposer)); - -// // Test: try to update the status from an EOA -// vm.prank(proposer); -// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); -// } - -// /** -// * @notice If the dispute does not exist, the call should revert -// */ -// function test_updateDisputeStatus_revertsIfInvalidDispute() public { -// bytes32 _disputeId = _getId(mockDispute); - -// oracle.mock_setDisputeCreatedAt(_disputeId, 0); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); - -// // Test: try to update the status -// vm.prank(address(resolutionModule)); -// oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active); -// } -// } - -// contract Oracle_Unit_ResolveDispute is BaseTest { -// /** -// * @notice Test if the resolution module is called and the event is emitted -// */ -// function test_resolveDispute_callsResolutionModule() public { -// // Mock the dispute -// bytes32 _disputeId = _getId(mockDispute); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - -// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); - -// // Mock and expect the resolution module call -// _mockAndExpect( -// address(resolutionModule), -// abi.encodeCall(IResolutionModule.resolveDispute, (_disputeId, mockRequest, mockResponse, mockDispute)), -// abi.encode() -// ); - -// // Check: emits DisputeResolved event? -// _expectEmit(address(oracle)); -// emit DisputeResolved(_disputeId, mockDispute, address(this)); - -// // Test: resolve the dispute -// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Test the revert when the function is called with an non-existent dispute id -// */ -// function test_resolveDispute_revertsIfInvalidDisputeId() public { -// oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _getId(mockDispute))); - -// // Test: try to resolve the dispute -// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Revert if the dispute doesn't exist -// */ -// function test_resolveDispute_revertsIfInvalidDispute() public { -// oracle.mock_setDisputeCreatedAt(_getId(mockDispute), 0); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); - -// // Test: try to resolve the dispute -// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Test the revert when the function is called with a dispute in unresolvable status -// */ -// function test_resolveDispute_revertsIfWrongDisputeStatus() public { -// bytes32 _disputeId = _getId(mockDispute); - -// for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { -// if (_status == uint256(IOracle.DisputeStatus.Active) || _status == uint256(IOracle.DisputeStatus.Escalated)) { -// continue; -// } - -// bytes32 _responseId = _getId(mockResponse); - -// // Mock the dispute -// oracle.mock_setDisputeOf(_responseId, _disputeId); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_status)); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotResolve.selector, _disputeId)); - -// // Test: try to resolve the dispute -// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); -// } -// } - -// /** -// * @notice Revert if the request has no resolution module configured -// */ -// function test_resolveDispute_revertsIfNoResolutionModule() public { -// // Clear the resolution module -// mockRequest.resolutionModule = address(0); -// bytes32 _requestId = _getId(mockRequest); - -// mockResponse.requestId = _requestId; -// bytes32 _responseId = _getId(mockResponse); - -// mockDispute.requestId = _requestId; -// mockDispute.responseId = _responseId; -// bytes32 _disputeId = _getId(mockDispute); - -// // Mock the dispute -// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Escalated); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NoResolutionModule.selector, _disputeId)); - -// // Test: try to resolve the dispute -// oracle.resolveDispute(mockRequest, mockResponse, mockDispute); -// } -// } - -// contract Oracle_Unit_AllowedModule is BaseTest { -// /** -// * @notice Test if the modules are recognized as allowed and random addresses aren't -// */ -// function test_allowedModule(address _notAModule) public { -// // Fuzz any address not in the modules of the request -// vm.assume( -// _notAModule != address(requestModule) && _notAModule != address(responseModule) -// && _notAModule != address(disputeModule) && _notAModule != address(resolutionModule) -// && _notAModule != address(finalityModule) -// ); - -// bytes32 _requestId = _getId(mockRequest); -// oracle.mock_addAllowedModule(_requestId, address(requestModule)); -// oracle.mock_addAllowedModule(_requestId, address(responseModule)); -// oracle.mock_addAllowedModule(_requestId, address(disputeModule)); -// oracle.mock_addAllowedModule(_requestId, address(resolutionModule)); -// oracle.mock_addAllowedModule(_requestId, address(finalityModule)); - -// // Check: the correct modules are recognized as valid -// assertTrue(oracle.allowedModule(_requestId, address(requestModule))); -// assertTrue(oracle.allowedModule(_requestId, address(responseModule))); -// assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); -// assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); -// assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); - -// // Check: any other address is not recognized as allowed module -// assertFalse(oracle.allowedModule(_requestId, _notAModule)); -// } -// } - -// contract Oracle_Unit_IsParticipant is BaseTest { -// /** -// * @notice Test if participants are recognized as such and random addresses aren't -// */ -// function test_isParticipant(bytes32 _requestId, address _notParticipant) public { -// vm.assume(_notParticipant != requester && _notParticipant != proposer && _notParticipant != disputer); - -// // Set valid participants -// oracle.mock_addParticipant(_requestId, requester); -// oracle.mock_addParticipant(_requestId, proposer); -// oracle.mock_addParticipant(_requestId, disputer); - -// // Check: the participants are recognized -// assertTrue(oracle.isParticipant(_requestId, requester)); -// assertTrue(oracle.isParticipant(_requestId, proposer)); -// assertTrue(oracle.isParticipant(_requestId, disputer)); - -// // Check: any other address is not recognized as a participant -// assertFalse(oracle.isParticipant(_requestId, _notParticipant)); -// } -// } - -// contract Oracle_Unit_Finalize is BaseTest { -// modifier withoutResponse() { -// mockResponse.requestId = bytes32(0); -// _; -// } - -// /** -// * @notice Finalizing with a valid response, the happy path -// * @dev The request might or might not use a dispute and a finality module, this is fuzzed -// */ -// function test_finalize_withResponse( -// bool _useResolutionAndFinality, -// address _caller -// ) public setResolutionAndFinality(_useResolutionAndFinality) { -// bytes32 _requestId = _getId(mockRequest); -// mockResponse.requestId = _requestId; -// bytes32 _responseId = _getId(mockResponse); - -// oracle.mock_addResponseId(_requestId, _responseId); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - -// // Mock the finalize call on all modules -// bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); - -// _mockAndExpect(address(requestModule), _calldata, abi.encode()); -// _mockAndExpect(address(responseModule), _calldata, abi.encode()); -// _mockAndExpect(address(disputeModule), _calldata, abi.encode()); - -// if (_useResolutionAndFinality) { -// _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); -// _mockAndExpect(address(finalityModule), _calldata, abi.encode()); -// } - -// // Check: emits OracleRequestFinalized event? -// _expectEmit(address(oracle)); -// emit OracleRequestFinalized(_requestId, _responseId, _caller); - -// // Test: finalize the request -// vm.prank(_caller); -// oracle.finalize(mockRequest, mockResponse); - -// assertEq(oracle.finalizedAt(_requestId), block.timestamp); -// } - -// /** -// * @notice Revert if the request doesn't exist -// */ -// function test_finalize_revertsIfInvalidRequest() public { -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), 0); -// vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); - -// vm.prank(requester); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Revert if the response doesn't exist -// */ -// function test_finalize_revertsIfInvalidResponse() public { -// bytes32 _requestId = _getId(mockRequest); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// oracle.mock_setResponseCreatedAt(_requestId, 0); - -// // Check: revert? -// vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); - -// // Test: finalize the request -// vm.prank(requester); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Finalizing an already finalized request -// */ -// function test_finalize_withResponse_revertsWhenAlreadyFinalized() public { -// bytes32 _requestId = _getId(mockRequest); -// bytes32 _responseId = _getId(mockResponse); - -// // Test: finalize a finalized request -// oracle.mock_setFinalizedAt(_requestId, block.timestamp); -// oracle.mock_addResponseId(_requestId, _responseId); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); - -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); -// vm.prank(requester); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Test the response validation, its requestId should match the id of the provided request -// */ -// function test_finalize_withResponse_revertsInvalidRequestId(bytes32 _requestId) public { -// vm.assume(_requestId != bytes32(0) && _requestId != _getId(mockRequest)); - -// mockResponse.requestId = _requestId; -// bytes32 _responseId = _getId(mockResponse); - -// // Store the response -// oracle.mock_addResponseId(_requestId, _responseId); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_requestId, block.timestamp); - -// // Test: finalize the request -// vm.expectRevert(ValidatorLib.ValidatorLib_InvalidResponseBody.selector); -// vm.prank(requester); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Finalizing a request with a successfully disputed response should revert -// */ -// function test_finalize_withResponse_revertsIfDisputedResponse(uint256 _status) public { -// vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); -// vm.assume(_status != uint256(IOracle.DisputeStatus.None)); -// vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); - -// bytes32 _requestId = _getId(mockRequest); -// bytes32 _responseId = _getId(mockResponse); -// bytes32 _disputeId = _getId(mockDispute); - -// // Submit a response to the request -// oracle.mock_addResponseId(_requestId, _responseId); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); -// oracle.mock_setDisputeOf(_responseId, _disputeId); -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Won); - -// // Check: reverts? -// vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); - -// // Test: finalize the request -// vm.prank(requester); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Finalizing with a blank response, meaning the request hasn't got any attention or the provided responses were invalid -// * @dev The request might or might not use a dispute and a finality module, this is fuzzed -// */ -// function test_finalize_withoutResponse( -// bool _useResolutionAndFinality, -// address _caller -// ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { -// vm.assume(_caller != address(0)); - -// bytes32 _requestId = _getId(mockRequest); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// mockResponse.requestId = bytes32(0); - -// // Create mock request and store it -// bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); - -// _mockAndExpect(address(requestModule), _calldata, abi.encode()); -// _mockAndExpect(address(responseModule), _calldata, abi.encode()); -// _mockAndExpect(address(disputeModule), _calldata, abi.encode()); - -// if (_useResolutionAndFinality) { -// _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); -// _mockAndExpect(address(finalityModule), _calldata, abi.encode()); -// } - -// // Check: emits OracleRequestFinalized event? -// _expectEmit(address(oracle)); -// emit OracleRequestFinalized(_requestId, bytes32(0), _caller); - -// // Test: finalize the request -// vm.prank(_caller); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Testing the finalization of a request with multiple responses all of which have been disputed -// * @dev The request might or might not use a dispute and a finality module, this is fuzzed -// */ -// function test_finalize_withoutResponse_withMultipleDisputedResponses( -// bool _useResolutionAndFinality, -// address _caller, -// uint8 _status, -// uint8 _numberOfResponses -// ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { -// vm.assume(_numberOfResponses < 5); - -// // All responses will have the same dispute status -// vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); -// vm.assume(_status != uint256(IOracle.DisputeStatus.None)); -// vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); - -// bytes32 _requestId = _getId(mockRequest); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); - -// IOracle.DisputeStatus _disputeStatus = IOracle.DisputeStatus(_status); - -// for (uint8 _i; _i < _numberOfResponses; _i++) { -// mockResponse.response = abi.encodePacked(_i); - -// // Compute the mock object ids -// bytes32 _responseId = _getId(mockResponse); -// mockDispute.responseId = _responseId; -// bytes32 _disputeId = _getId(mockDispute); - -// // The response must be disputed -// oracle.mock_addResponseId(_requestId, _responseId); -// oracle.mock_setDisputeOf(_responseId, _disputeId); -// oracle.mock_setDisputeStatus(_disputeId, _disputeStatus); -// } - -// mockResponse.response = bytes(''); - -// // The finalization should come through -// // Check: emits OracleRequestFinalized event? -// _expectEmit(address(oracle)); -// emit OracleRequestFinalized(_requestId, bytes32(0), _caller); - -// // Test: finalize the request -// vm.prank(_caller); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Finalizing an already finalized response shouldn't be possible -// */ -// function test_finalize_withoutResponse_revertsWhenAlreadyFinalized(address _caller) public withoutResponse { -// bytes32 _requestId = _getId(mockRequest); - -// // Override the finalizedAt to make it be finalized -// oracle.mock_setFinalizedAt(_requestId, block.timestamp); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - -// // Test: finalize a finalized request -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); -// vm.prank(_caller); -// oracle.finalize(mockRequest, mockResponse); -// } - -// /** -// * @notice Finalizing a request with a non-disputed response should revert -// */ -// function test_finalize_withoutResponse_revertsWithNonDisputedResponse(bytes32 _responseId) public withoutResponse { -// vm.assume(_responseId != bytes32(0)); - -// bytes32 _requestId = _getId(mockRequest); - -// // Submit a response to the request -// oracle.mock_addResponseId(_requestId, _responseId); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - -// // Check: reverts? -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_FinalizableResponseExists.selector, _responseId)); - -// // Test: finalize the request -// vm.prank(requester); -// oracle.finalize(mockRequest, mockResponse); -// } -// } - -// contract Oracle_Unit_EscalateDispute is BaseTest { -// /** -// * @notice Test if the dispute is escalated correctly and the event is emitted -// */ -// function test_escalateDispute() public { -// bytes32 _disputeId = _getId(mockDispute); - -// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - -// // Mock and expect the dispute module call -// _mockAndExpect( -// address(disputeModule), -// abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), -// abi.encode() -// ); - -// // Mock and expect the resolution module call -// _mockAndExpect( -// address(resolutionModule), -// abi.encodeCall(IResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)), -// abi.encode() -// ); - -// // Expect dispute escalated event -// _expectEmit(address(oracle)); -// emit DisputeEscalated(address(this), _disputeId, mockDispute); - -// // Test: escalate the dispute -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - -// // Check: The dispute has been escalated -// assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); -// } - -// /** -// * @notice Should not revert if no resolution module was configured -// */ -// function test_escalateDispute_noResolutionModule() public { -// mockRequest.resolutionModule = address(0); - -// bytes32 _requestId = _getId(mockRequest); - -// mockResponse.requestId = _requestId; -// bytes32 _responseId = _getId(mockResponse); - -// mockDispute.requestId = _requestId; -// mockDispute.responseId = _responseId; -// bytes32 _disputeId = _getId(mockDispute); - -// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); -// oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); -// oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); -// oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - -// // Mock and expect the dispute module call -// _mockAndExpect( -// address(disputeModule), -// abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), -// abi.encode() -// ); - -// // Expect dispute escalated event -// _expectEmit(address(oracle)); -// emit DisputeEscalated(address(this), _disputeId, mockDispute); - -// // Test: escalate the dispute -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); - -// // Check: The dispute has been escalated -// assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); -// } - -// /** -// * /** -// * @notice Revert if the dispute doesn't exist -// */ -// function test_escalateDispute_revertsIfInvalidDispute() public { -// bytes32 _disputeId = _getId(mockDispute); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, 0); -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); - -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); -// } - -// /** -// * @notice Revert if the provided dispute does not match the request or the response -// */ -// function test_escalateDispute_revertsIfDisputeNotValid() public { -// bytes32 _disputeId = _getId(mockDispute); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); - -// // Test: escalate the dispute -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); -// } - -// function test_escalateDispute_revertsIfDisputeNotActive() public { -// bytes32 _disputeId = _getId(mockDispute); -// oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); -// oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); -// oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); -// oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); - -// vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); - -// // Test: escalate the dispute -// oracle.escalateDispute(mockRequest, mockResponse, mockDispute); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {IModule} from '../../interfaces/IModule.sol'; +import {IOracle} from '../../interfaces/IOracle.sol'; + +import {IDisputeModule} from '../../interfaces/modules/dispute/IDisputeModule.sol'; + +import {IAccessController} from '../../interfaces/IAccessController.sol'; +import {IAccessControlModule} from '../../interfaces/modules/accessControl/IAccessControlModule.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 {Oracle} from '../../contracts/Oracle.sol'; +import {Helpers} from '../utils/Helpers.sol'; + +import {ValidatorLib} from '../../libraries/ValidatorLib.sol'; + +/** + * @notice Harness to deploy and test Oracle + */ +contract MockOracle is Oracle { + constructor() Oracle() {} + + function mock_addParticipant(bytes32 _requestId, address _participant) external { + isParticipant[_requestId][_participant] = true; + } + + function mock_addAllowedModule(bytes32 _requestId, address _module) external { + allowedModule[_requestId][_module] = true; + } + + function mock_setFinalizedResponseId(bytes32 _requestId, bytes32 _finalizedResponseId) external { + finalizedResponseId[_requestId] = _finalizedResponseId; + } + + function mock_setFinalizedAt(bytes32 _requestId, uint256 _finalizedAt) external { + finalizedAt[_requestId] = _finalizedAt; + } + + function mock_setDisputeOf(bytes32 _responseId, bytes32 _disputeId) external { + disputeOf[_responseId] = _disputeId; + } + + function mock_setDisputeStatus(bytes32 _disputeId, IOracle.DisputeStatus _status) external { + disputeStatus[_disputeId] = _status; + } + + function mock_setRequestId(uint256 _nonce, bytes32 _requestId) external { + nonceToRequestId[_nonce] = _requestId; + } + + function mock_setRequestCreatedAt(bytes32 _requestId, uint256 _requestCreatedAt) external { + requestCreatedAt[_requestId] = _requestCreatedAt; + } + + function mock_setResponseCreatedAt(bytes32 _responseId, uint256 _responseCreatedAt) external { + responseCreatedAt[_responseId] = _responseCreatedAt; + } + + function mock_setDisputeCreatedAt(bytes32 _disputeId, uint256 _disputeCreatedAt) external { + disputeCreatedAt[_disputeId] = _disputeCreatedAt; + } + + function mock_setTotalRequestCount(uint256 _totalRequestCount) external { + totalRequestCount = _totalRequestCount; + } + + function mock_addResponseId(bytes32 _requestId, bytes32 _responseId) external { + _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); + } +} + +/** + * @title Oracle Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + MockOracle public 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')); + IAccessControlModule public accessControlModule = IAccessControlModule(_mockContract('accessControlModule')); + + // Mock IPFS hash + bytes32 internal _ipfsHash = bytes32('QmR4uiJH654k3Ta2uLLQ8r'); + + // Events + event RequestCreated(bytes32 indexed _requestId, IOracle.Request _request, bytes32 _ipfsHash); + event ResponseProposed(bytes32 indexed _requestId, bytes32 indexed _responseId, IOracle.Response _response); + event ResponseDisputed(bytes32 indexed _responseId, bytes32 indexed _disputeId, IOracle.Dispute _dispute); + event OracleRequestFinalized(bytes32 indexed _requestId, bytes32 indexed _responseId); + event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); + event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); + event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute); + + function setUp() public virtual { + oracle = new MockOracle(); + + mockRequest.requestModule = address(requestModule); + mockRequest.responseModule = address(responseModule); + mockRequest.disputeModule = address(disputeModule); + mockRequest.resolutionModule = address(resolutionModule); + mockRequest.finalityModule = address(finalityModule); + mockRequest.accessControlModule = address(accessControlModule); + + mockResponse.requestId = _getId(mockRequest); + mockDispute.requestId = mockResponse.requestId; + mockDispute.responseId = _getId(mockResponse); + } + + /** + * @notice If no dispute and finality module used, set them to address(0) + */ + modifier setResolutionAndFinality(bool _useResolutionAndFinality) { + if (!_useResolutionAndFinality) { + resolutionModule = IResolutionModule(address(0)); + finalityModule = IFinalityModule(address(0)); + } + _; + } +} + +contract Oracle_Unit_CreateRequest is BaseTest { + modifier happyPath() { + mockAccessControl.user = requester; + vm.startPrank(requester); + _; + } + /** + * @notice Test the request creation with correct arguments and nonce increment + * @dev The request might or might not use a dispute and a finality module, this is fuzzed + */ + + function test_createRequest( + bool _useResolutionAndFinality, + bytes calldata _requestData, + bytes calldata _responseData, + bytes calldata _disputeData, + bytes calldata _resolutionData, + bytes calldata _finalityData + ) public setResolutionAndFinality(_useResolutionAndFinality) happyPath { + uint256 _initialNonce = oracle.totalRequestCount(); + + // Create the request + mockRequest.requestModuleData = _requestData; + mockRequest.responseModuleData = _responseData; + mockRequest.disputeModuleData = _disputeData; + mockRequest.resolutionModuleData = _resolutionData; + mockRequest.finalityModuleData = _finalityData; + mockRequest.requester = requester; + mockRequest.nonce = uint96(oracle.totalRequestCount()); + + // Compute the associated request id + bytes32 _theoreticalRequestId = _getId(mockRequest); + + // Check: emits RequestCreated event? + _expectEmit(address(oracle)); + emit RequestCreated(_getId(mockRequest), mockRequest, _ipfsHash); + + // Test: create the request + bytes32 _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + // Check: Adds the requester to the list of participants + assertTrue(oracle.isParticipant(_requestId, requester)); + + // Check: Saves the number of the block + assertEq(oracle.requestCreatedAt(_requestId), block.timestamp); + + // Check: Sets allowedModules + assertTrue(oracle.allowedModule(_requestId, address(requestModule))); + assertTrue(oracle.allowedModule(_requestId, address(responseModule))); + assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); + + if (_useResolutionAndFinality) { + assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); + assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); + } + + // Check: Maps the nonce to the requestId + assertEq(oracle.nonceToRequestId(mockRequest.nonce), _requestId); + + // Check: correct request id returned? + assertEq(_requestId, _theoreticalRequestId); + + // Check: nonce incremented? + assertEq(oracle.totalRequestCount(), _initialNonce + 1); + } + + /** + * @notice Check that creating a request with a nonce that already exists reverts + */ + function test_createRequest_revertsIfInvalidNonce(uint256 _nonce) public happyPath { + vm.assume(_nonce != oracle.totalRequestCount()); + + // Set the nonce + mockRequest.nonce = uint96(_nonce); + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); + + // Test: try to create the request + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + } + + /** + * @notice Check that creating a request with a misconfigured requester reverts + */ + function test_createRequest_revertsIfInvalidRequester(address _requester) public happyPath { + vm.assume(_requester != requester); + + // Set the nonce + mockRequest.requester = _requester; + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidRequestBody.selector); + + // Test: try to create the request + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + } +} + +contract Oracle_Unit_CreateRequests is BaseTest { + /** + * @notice Test creation of requests in batch mode. + */ + function test_createRequests( + bytes calldata _requestData, + bytes calldata _responseData, + bytes calldata _disputeData + ) public { + uint256 _initialNonce = oracle.totalRequestCount(); + uint256 _requestsAmount = 5; + IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); + bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); + bool _useResolutionAndFinality = _requestData.length % 2 == 0; + bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); + IAccessController.AccessControl[] memory _accessControls = new IAccessController.AccessControl[](_requestsAmount); + + // Generate requests batch + for (uint256 _i = 0; _i < _requestsAmount; _i++) { + mockRequest.requestModuleData = _requestData; + mockRequest.responseModuleData = _responseData; + mockRequest.disputeModuleData = _disputeData; + mockRequest.requester = requester; + mockRequest.nonce = uint96(oracle.totalRequestCount() + _i); + + bytes32 _theoreticalRequestId = _getId(mockRequest); + _requests[_i] = mockRequest; + _precalculatedIds[_i] = _theoreticalRequestId; + _ipfsHashes[_i] = keccak256(abi.encode(_theoreticalRequestId, mockRequest.nonce)); + + _accessControls[_i].user = requester; + + // Check: emits RequestCreated event? + _expectEmit(address(oracle)); + emit RequestCreated(_theoreticalRequestId, mockRequest, _ipfsHashes[_i]); + } + + vm.prank(requester); + bytes32[] memory _requestsIds = oracle.createRequests(_requests, _ipfsHashes, _accessControls); + + for (uint256 _i = 0; _i < _requestsIds.length; _i++) { + assertEq(_requestsIds[_i], _precalculatedIds[_i]); + + // Check: Adds the requester to the list of participants + assertTrue(oracle.isParticipant(_requestsIds[_i], requester)); + + // Check: Saves the number of the block + assertEq(oracle.requestCreatedAt(_requestsIds[_i]), block.timestamp); + + // Check: Sets allowedModules + assertTrue(oracle.allowedModule(_requestsIds[_i], address(requestModule))); + assertTrue(oracle.allowedModule(_requestsIds[_i], address(responseModule))); + assertTrue(oracle.allowedModule(_requestsIds[_i], address(disputeModule))); + + if (_useResolutionAndFinality) { + assertTrue(oracle.allowedModule(_requestsIds[_i], address(resolutionModule))); + assertTrue(oracle.allowedModule(_requestsIds[_i], address(finalityModule))); + } + + // Check: Maps the nonce to the requestId + assertEq(oracle.nonceToRequestId(_requests[_i].nonce), _requestsIds[_i]); + } + + uint256 _newNonce = oracle.totalRequestCount(); + assertEq(_newNonce, _initialNonce + _requestsAmount); + } + + /** + * @notice Test creation of requests in batch mode with nonce 0. + */ + function test_createRequestsWithNonceZero( + bytes calldata _requestData, + bytes calldata _responseData, + bytes calldata _disputeData + ) public { + uint256 _initialNonce = oracle.totalRequestCount(); + uint256 _requestsAmount = 5; + IOracle.Request[] memory _requests = new IOracle.Request[](_requestsAmount); + bytes32[] memory _precalculatedIds = new bytes32[](_requestsAmount); + bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); + IAccessController.AccessControl[] memory _accessControls = new IAccessController.AccessControl[](_requestsAmount); + + mockRequest.requestModuleData = _requestData; + mockRequest.responseModuleData = _responseData; + mockRequest.disputeModuleData = _disputeData; + mockRequest.requester = requester; + mockRequest.nonce = uint96(0); + + bytes32 _theoreticalRequestId = _getId(mockRequest); + bytes32 _ipfsHash = keccak256(abi.encode(_theoreticalRequestId, uint96(0))); + + // Generate requests batch + for (uint256 _i = 0; _i < _requestsAmount; _i++) { + _requests[_i] = mockRequest; + _precalculatedIds[_i] = _theoreticalRequestId; + _ipfsHashes[_i] = _ipfsHash; + _accessControls[_i].user = requester; + } + + vm.prank(requester); + oracle.createRequests(_requests, _ipfsHashes, _accessControls); + + uint256 _newNonce = oracle.totalRequestCount(); + assertEq(_newNonce, _initialNonce + _requestsAmount); + } +} + +contract Oracle_Unit_ListRequestIds is BaseTest { + /** + * @notice Test list requests ids, fuzz the batch size + */ + function test_listRequestIds(uint256 _numberOfRequests) public { + // 0 to 10 request to list, fuzzed + _numberOfRequests = bound(_numberOfRequests, 0, 10); + + bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); + + for (uint256 _i; _i < _numberOfRequests; _i++) { + mockRequest.nonce = uint96(_i); + bytes32 _requestId = _getId(mockRequest); + _mockRequestIds[_i] = _requestId; + oracle.mock_setRequestId(_i, _requestId); + } + + oracle.mock_setTotalRequestCount(_numberOfRequests); + + // Test: fetching the requests + bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); + + // Check: enough request returned? + assertEq(_requestsIds.length, _numberOfRequests); + + // Check: correct requests returned (dummy are incremented)? + for (uint256 _i; _i < _numberOfRequests; _i++) { + assertEq(_requestsIds[_i], _mockRequestIds[_i]); + } + } + + /** + * @notice Test the request listing if asking for more request than it exists + */ + function test_listRequestIds_tooManyRequested(uint256 _numberOfRequests) public { + // 1 to 10 request to list, fuzzed + _numberOfRequests = bound(_numberOfRequests, 1, 10); + + bytes32[] memory _mockRequestIds = new bytes32[](_numberOfRequests); + + for (uint256 _i; _i < _numberOfRequests; _i++) { + mockRequest.nonce = uint96(_i); + bytes32 _requestId = _getId(mockRequest); + _mockRequestIds[_i] = _requestId; + oracle.mock_setRequestId(_i, _requestId); + } + + oracle.mock_setTotalRequestCount(_numberOfRequests); + + // Test: fetching 1 extra request + bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests + 1); + + // Check: correct number of request returned? + assertEq(_requestsIds.length, _numberOfRequests); + + // Check: correct data? + for (uint256 _i; _i < _numberOfRequests; _i++) { + assertEq(_requestsIds[_i], _mockRequestIds[_i]); + } + + // Test: starting from an index outside of the range + _requestsIds = oracle.listRequestIds(_numberOfRequests + 1, _numberOfRequests); + assertEq(_requestsIds.length, 0); + } + + /** + * @notice Test the request listing if there are no requests encoded + */ + function test_listRequestIds_zeroToReturn(uint256 _numberOfRequests) public { + // Test: fetch any number of requests + bytes32[] memory _requestsIds = oracle.listRequestIds(0, _numberOfRequests); + + // Check; 0 returned? + assertEq(_requestsIds.length, 0); + } +} + +contract Oracle_Unit_ProposeResponse is BaseTest { + modifier happyPath() { + mockAccessControl.user = proposer; + vm.startPrank(proposer); + _; + } + /** + * @notice Proposing a response should call the response module, emit an event and return the response id + */ + + function test_proposeResponse_emitsEvent(bytes calldata _responseData) public happyPath { + bytes32 _requestId = _getId(mockRequest); + + // Update mock response + mockResponse.response = _responseData; + + // Compute the response ID + bytes32 _responseId = _getId(mockResponse); + + // Set the request creation time + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + + // Mock and expect the responseModule propose call: + _mockAndExpect( + address(responseModule), + abi.encodeCall(IResponseModule.propose, (mockRequest, mockResponse, proposer)), + abi.encode(mockResponse) + ); + + // Check: emits ResponseProposed event? + _expectEmit(address(oracle)); + emit ResponseProposed(_requestId, _responseId, mockResponse); + + // Test: propose the response + bytes32 _actualResponseId = oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + mockResponse.response = bytes('secondResponse'); + + // Check: emits ResponseProposed event? + _expectEmit(address(oracle)); + emit ResponseProposed(_requestId, _getId(mockResponse), mockResponse); + + bytes32 _secondResponseId = oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + // Check: correct response id returned? + assertEq(_actualResponseId, _responseId); + + // Check: responseId are unique? + assertNotEq(_secondResponseId, _responseId); + + // Check: correct response id stored in the id list and unique? + bytes32[] memory _responseIds = oracle.getResponseIds(_requestId); + assertEq(_responseIds.length, 2); + assertEq(_responseIds[0], _responseId); + assertEq(_responseIds[1], _secondResponseId); + } + + function test_proposeResponse_revertsIfInvalidRequest() public happyPath { + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidRequest.selector); + + // Test: try to propose a response with an invalid request + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Revert if the access control module returns false + */ + function test_proposeResponse_revertsIfInvalidAccessControlData(address _caller) public { + vm.assume(_caller != proposer); + + mockRequest.accessControlModule = address(0); + mockAccessControl.user = proposer; + + // Check: revert? + vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + + // Test: try to propose a response from a random address + vm.prank(_caller); + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Revert if the caller is not the proposer nor the dispute module + */ + function test_proposeResponse_revertsIfInvalidCaller(address _caller) public { + vm.assume(_caller != proposer && _caller != address(disputeModule)); + + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + + mockAccessControl.user = _caller; + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); + + // Test: try to propose a response from a random address + vm.prank(_caller); + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Revert if the response has been already proposed + */ + function test_proposeResponse_revertsIfDuplicateResponse() public happyPath { + // Set the request creation time + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + + // Test: propose a response + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + // Check: revert? + vm.expectRevert(IOracle.Oracle_ResponseAlreadyProposed.selector); + + // Test: try to propose the same response again + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Proposing a response to a finalized request should fail + */ + function test_proposeResponse_revertsIfAlreadyFinalized(uint128 _finalizedAt) public happyPath { + vm.assume(_finalizedAt > 0); + + // Set the finalization time + bytes32 _requestId = _getId(mockRequest); + oracle.mock_setFinalizedAt(_requestId, _finalizedAt); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + + // Check: Reverts if already finalized? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, (_requestId))); + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } +} + +contract Oracle_Unit_DisputeResponse is BaseTest { + bytes32 internal _responseId; + bytes32 internal _disputeId; + + function setUp() public override { + super.setUp(); + + _responseId = _getId(mockResponse); + _disputeId = _getId(mockDispute); + + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + } + + modifier happyPath() { + mockAccessControl.user = disputer; + vm.startPrank(disputer); + _; + } + + /** + * @notice Calls the dispute module, sets the correct status of the dispute, emits events + */ + function test_disputeResponse_emitsEvent() public happyPath { + // Add a response to the request + oracle.mock_addResponseId(_getId(mockRequest), _responseId); + + for (uint256 _i; _i < uint256(type(IOracle.DisputeStatus).max); _i++) { + // Set the new status + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_i)); + + // Mock and expect the disputeModule disputeResponse call + _mockAndExpect( + address(disputeModule), + abi.encodeCall(IDisputeModule.disputeResponse, (mockRequest, mockResponse, mockDispute)), + abi.encode(mockDispute) + ); + + // Check: emits ResponseDisputed event? + _expectEmit(address(oracle)); + emit ResponseDisputed(_responseId, _disputeId, mockDispute); + + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // Reset the dispute of the response + oracle.mock_setDisputeOf(_responseId, bytes32(0)); + } + } + + /** + * @notice Reverts if the dispute proposer and response proposer are not same + */ + function test_disputeResponse_revertIfProposerIsNotValid(address _otherProposer) public happyPath { + vm.assume(_otherProposer != proposer); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidProposer.selector); + + mockDispute.proposer = _otherProposer; + + // Test: try to dispute the response + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Reverts if the response doesn't exist + */ + function test_disputeResponse_revertIfInvalidResponse() public happyPath { + oracle.mock_setResponseCreatedAt(_getId(mockResponse), 0); + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); + + // Test: try to dispute the response + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Revert if the access control module returns false + */ + function test_disputeResponse_revertsIfInvalidAccessControlData(address _caller) public { + vm.assume(_caller != disputer); + mockRequest.accessControlModule = address(0); + mockAccessControl.user = disputer; + + // Check: revert? + vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + + // Test: try to propose a response from a random address + vm.prank(_caller); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Reverts if the caller and the disputer are not the same + */ + function test_disputeResponse_revertIfWrongDisputer(address _caller) public { + vm.assume(_caller != disputer); + + mockAccessControl.user = _caller; + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidDisputer.selector); + + // Test: try to dispute the response again + vm.prank(_caller); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Reverts if the request has already been disputed + */ + function test_disputeResponse_revertIfAlreadyDisputed() public happyPath { + // Check: revert? + oracle.mock_setDisputeOf(_responseId, _disputeId); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); + + // Test: try to dispute the response again + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } +} + +contract Oracle_Unit_UpdateDisputeStatus is BaseTest { + modifier happyPath() { + mockAccessControl.user = address(disputeModule); + vm.startPrank(address(disputeModule)); + _; + } + /** + * @notice Test if the dispute status is updated correctly and the event is emitted + * @dev This is testing every combination of previous and new status + */ + + function test_updateDisputeStatus_emitsEvent() public happyPath { + bytes32 _requestId = _getId(mockRequest); + oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); + + // Try every initial status + for (uint256 _previousStatus; _previousStatus < uint256(type(IOracle.DisputeStatus).max); _previousStatus++) { + // Try every new status + for (uint256 _newStatus; _newStatus < uint256(type(IOracle.DisputeStatus).max); _newStatus++) { + // Set the dispute status + mockDispute.requestId = _requestId; + bytes32 _disputeId = _getId(mockDispute); + + // Mock the dispute + oracle.mock_setDisputeOf(_getId(mockResponse), _getId(mockDispute)); + + // Mock and expect the disputeModule onDisputeStatusChange call + _mockAndExpect( + address(disputeModule), + abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), + abi.encode() + ); + + // Check: emits DisputeStatusUpdated event? + _expectEmit(address(oracle)); + emit DisputeStatusUpdated(_disputeId, mockDispute, IOracle.DisputeStatus(_newStatus)); + + // Test: change the status + oracle.updateDisputeStatus( + mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus), mockAccessControl + ); + + // Check: correct status stored? + assertEq(_newStatus, uint256(oracle.disputeStatus(_disputeId))); + } + } + } + + /** + * @notice Providing a dispute that does not match the response should revert + */ + function test_updateDisputeStatus_revertsIfInvalidDisputeId(bytes32 _randomId, uint256 _newStatus) public happyPath { + // 0 to 3 status, fuzzed + _newStatus = bound(_newStatus, 0, 3); + bytes32 _disputeId = _getId(mockDispute); + vm.assume(_randomId != _disputeId); + + // Setting a random dispute id, not matching the mockDispute + oracle.mock_setDisputeOf(_getId(mockResponse), _randomId); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); + + // Test: Try to update the dispute + oracle.updateDisputeStatus( + mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus), mockAccessControl + ); + } + + /** + * @notice Revert if the access control module returns false + */ + function test_updateDisputeStatus_revertsIfInvalidAccessControlData(address _caller) public { + vm.assume(_caller != address(disputeModule)); + + mockRequest.accessControlModule = address(0); + mockAccessControl.user = address(disputeModule); + + // Check: revert? + vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + + // Test: try to propose a response from a random address + vm.prank(_caller); + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active, mockAccessControl); + } + + /** + * @notice If the sender is not the dispute/resolution module, the call should revert + */ + function test_updateDisputeStatus_revertsIfWrongCaller(uint256 _newStatus) public { + // 0 to 3 status, fuzzed + _newStatus = bound(_newStatus, 0, 3); + + bytes32 _disputeId = _getId(mockDispute); + bytes32 _responseId = _getId(mockResponse); + + // Mock the dispute + oracle.mock_setDisputeOf(_responseId, _disputeId); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + + mockAccessControl.user = proposer; + vm.mockCall( + address(accessControlModule), abi.encodeWithSelector(IAccessControlModule.hasAccess.selector), abi.encode(true) + ); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NotDisputeOrResolutionModule.selector, proposer)); + + // Test: try to update the status from an EOA + vm.prank(proposer); + oracle.updateDisputeStatus( + mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus), mockAccessControl + ); + } + + /** + * @notice If the dispute does not exist, the call should revert + */ + function test_updateDisputeStatus_revertsIfInvalidDispute() public { + bytes32 _disputeId = _getId(mockDispute); + + mockAccessControl.user = address(resolutionModule); + + oracle.mock_setDisputeCreatedAt(_disputeId, 0); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); + + // Test: try to update the status + vm.prank(address(resolutionModule)); + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active, mockAccessControl); + } +} + +contract Oracle_Unit_ResolveDispute is BaseTest { + modifier happyPath() { + mockAccessControl.user = address(resolutionModule); + vm.startPrank(address(resolutionModule)); + _; + } + /** + * @notice Test if the resolution module is called and the event is emitted + */ + + function test_resolveDispute_callsResolutionModule() public happyPath { + // Mock the dispute + bytes32 _disputeId = _getId(mockDispute); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + + oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); + + // Mock and expect the resolution module call + _mockAndExpect( + address(resolutionModule), + abi.encodeCall(IResolutionModule.resolveDispute, (_disputeId, mockRequest, mockResponse, mockDispute)), + abi.encode() + ); + + // Check: emits DisputeResolved event? + _expectEmit(address(oracle)); + emit DisputeResolved(_disputeId, mockDispute); + + // Test: resolve the dispute + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Revert if the access control module returns false + */ + function test_resolveDispute_revertsIfInvalidAccessControlData(address _caller) public { + vm.assume(_caller != address(resolutionModule)); + + mockRequest.accessControlModule = address(0); + mockAccessControl.user = address(resolutionModule); + + // Check: revert? + vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + + // Test: try to propose a response from a random address + vm.prank(_caller); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Test the revert when the function is called with an non-existent dispute id + */ + function test_resolveDispute_revertsIfInvalidDisputeId() public happyPath { + oracle.mock_setDisputeCreatedAt(_getId(mockDispute), block.timestamp); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _getId(mockDispute))); + + // Test: try to resolve the dispute + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Revert if the dispute doesn't exist + */ + function test_resolveDispute_revertsIfInvalidDispute() public happyPath { + oracle.mock_setDisputeCreatedAt(_getId(mockDispute), 0); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); + + // Test: try to resolve the dispute + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Test the revert when the function is called with a dispute in unresolvable status + */ + function test_resolveDispute_revertsIfWrongDisputeStatus() public happyPath { + bytes32 _disputeId = _getId(mockDispute); + + for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { + if (_status == uint256(IOracle.DisputeStatus.Active) || _status == uint256(IOracle.DisputeStatus.Escalated)) { + continue; + } + + bytes32 _responseId = _getId(mockResponse); + + // Mock the dispute + oracle.mock_setDisputeOf(_responseId, _disputeId); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus(_status)); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotResolve.selector, _disputeId)); + + // Test: try to resolve the dispute + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + } + + /** + * @notice Revert if the request has no resolution module configured + */ + function test_resolveDispute_revertsIfNoResolutionModule() public happyPath { + // Clear the resolution module + mockRequest.resolutionModule = address(0); + bytes32 _requestId = _getId(mockRequest); + + mockResponse.requestId = _requestId; + bytes32 _responseId = _getId(mockResponse); + + mockDispute.requestId = _requestId; + mockDispute.responseId = _responseId; + bytes32 _disputeId = _getId(mockDispute); + + // Mock the dispute + oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Escalated); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NoResolutionModule.selector, _disputeId)); + + // Test: try to resolve the dispute + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } +} + +contract Oracle_Unit_AllowedModule is BaseTest { + /** + * @notice Test if the modules are recognized as allowed and random addresses aren't + */ + function test_allowedModule(address _notAModule) public { + // Fuzz any address not in the modules of the request + vm.assume( + _notAModule != address(requestModule) && _notAModule != address(responseModule) + && _notAModule != address(disputeModule) && _notAModule != address(resolutionModule) + && _notAModule != address(finalityModule) + ); + + bytes32 _requestId = _getId(mockRequest); + oracle.mock_addAllowedModule(_requestId, address(requestModule)); + oracle.mock_addAllowedModule(_requestId, address(responseModule)); + oracle.mock_addAllowedModule(_requestId, address(disputeModule)); + oracle.mock_addAllowedModule(_requestId, address(resolutionModule)); + oracle.mock_addAllowedModule(_requestId, address(finalityModule)); + + // Check: the correct modules are recognized as valid + assertTrue(oracle.allowedModule(_requestId, address(requestModule))); + assertTrue(oracle.allowedModule(_requestId, address(responseModule))); + assertTrue(oracle.allowedModule(_requestId, address(disputeModule))); + assertTrue(oracle.allowedModule(_requestId, address(resolutionModule))); + assertTrue(oracle.allowedModule(_requestId, address(finalityModule))); + + // Check: any other address is not recognized as allowed module + assertFalse(oracle.allowedModule(_requestId, _notAModule)); + } +} + +contract Oracle_Unit_IsParticipant is BaseTest { + /** + * @notice Test if participants are recognized as such and random addresses aren't + */ + function test_isParticipant(bytes32 _requestId, address _notParticipant) public { + vm.assume(_notParticipant != requester && _notParticipant != proposer && _notParticipant != disputer); + + // Set valid participants + oracle.mock_addParticipant(_requestId, requester); + oracle.mock_addParticipant(_requestId, proposer); + oracle.mock_addParticipant(_requestId, disputer); + + // Check: the participants are recognized + assertTrue(oracle.isParticipant(_requestId, requester)); + assertTrue(oracle.isParticipant(_requestId, proposer)); + assertTrue(oracle.isParticipant(_requestId, disputer)); + + // Check: any other address is not recognized as a participant + assertFalse(oracle.isParticipant(_requestId, _notParticipant)); + } +} + +contract Oracle_Unit_Finalize is BaseTest { + modifier withoutResponse() { + mockResponse.requestId = bytes32(0); + _; + } + + modifier happyPath() { + mockAccessControl.user = address(requester); + vm.startPrank(address(requester)); + _; + } + + /** + * @notice Finalizing with a valid response, the happy path + * @dev The request might or might not use a dispute and a finality module, this is fuzzed + */ + function test_finalize_withResponse( + bool _useResolutionAndFinality, + address _caller + ) public setResolutionAndFinality(_useResolutionAndFinality) { + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + bytes32 _responseId = _getId(mockResponse); + + mockAccessControl.user = _caller; + + oracle.mock_addResponseId(_requestId, _responseId); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + + // Mock the finalize call on all modules + bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); + + _mockAndExpect(address(requestModule), _calldata, abi.encode()); + _mockAndExpect(address(responseModule), _calldata, abi.encode()); + _mockAndExpect(address(disputeModule), _calldata, abi.encode()); + + if (_useResolutionAndFinality) { + _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); + _mockAndExpect(address(finalityModule), _calldata, abi.encode()); + } + + // Check: emits OracleRequestFinalized event? + _expectEmit(address(oracle)); + emit OracleRequestFinalized(_requestId, _responseId); + + // Test: finalize the request + vm.prank(_caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + + assertEq(oracle.finalizedAt(_requestId), block.timestamp); + } + + /** + * @notice Revert if the request doesn't exist + */ + function test_finalize_revertsIfInvalidRequest() public happyPath { + oracle.mock_setRequestCreatedAt(_getId(mockRequest), 0); + vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + function test_finalize_revertsIfInvalidAccessControlData(address _caller) public { + vm.assume(_caller != address(requester)); + + mockRequest.accessControlModule = address(0); + mockAccessControl.user = address(requester); + + // Check: revert? + vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + + // Test: try to finalize the request from a random address + vm.prank(_caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Revert if the response doesn't exist + */ + function test_finalize_revertsIfInvalidResponse() public happyPath { + bytes32 _requestId = _getId(mockRequest); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + oracle.mock_setResponseCreatedAt(_requestId, 0); + + // Check: revert? + vm.expectRevert(IOracle.Oracle_InvalidResponse.selector); + + // Test: finalize the request + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Finalizing an already finalized request + */ + function test_finalize_withResponse_revertsWhenAlreadyFinalized() public happyPath { + bytes32 _requestId = _getId(mockRequest); + bytes32 _responseId = _getId(mockResponse); + + // Test: finalize a finalized request + oracle.mock_setFinalizedAt(_requestId, block.timestamp); + oracle.mock_addResponseId(_requestId, _responseId); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Test the response validation, its requestId should match the id of the provided request + */ + function test_finalize_withResponse_revertsInvalidRequestId(bytes32 _requestId) public happyPath { + vm.assume(_requestId != bytes32(0) && _requestId != _getId(mockRequest)); + + mockResponse.requestId = _requestId; + bytes32 _responseId = _getId(mockResponse); + + // Store the response + oracle.mock_addResponseId(_requestId, _responseId); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_requestId, block.timestamp); + + // Test: finalize the request + vm.expectRevert(ValidatorLib.ValidatorLib_InvalidResponseBody.selector); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Finalizing a request with a successfully disputed response should revert + */ + function test_finalize_withResponse_revertsIfDisputedResponse(uint256 _status) public happyPath { + vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); + vm.assume(_status != uint256(IOracle.DisputeStatus.None)); + vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); + + bytes32 _requestId = _getId(mockRequest); + bytes32 _responseId = _getId(mockResponse); + bytes32 _disputeId = _getId(mockDispute); + + // Submit a response to the request + oracle.mock_addResponseId(_requestId, _responseId); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + oracle.mock_setDisputeOf(_responseId, _disputeId); + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Won); + + // Check: reverts? + vm.expectRevert(IOracle.Oracle_InvalidFinalizedResponse.selector); + + // Test: finalize the request + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Finalizing with a blank response, meaning the request hasn't got any attention or the provided responses were invalid + * @dev The request might or might not use a dispute and a finality module, this is fuzzed + */ + function test_finalize_withoutResponse( + bool _useResolutionAndFinality, + address _caller + ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { + vm.assume(_caller != address(0)); + + bytes32 _requestId = _getId(mockRequest); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + mockResponse.requestId = bytes32(0); + mockAccessControl.user = _caller; + + // Create mock request and store it + bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); + + _mockAndExpect(address(requestModule), _calldata, abi.encode()); + _mockAndExpect(address(responseModule), _calldata, abi.encode()); + _mockAndExpect(address(disputeModule), _calldata, abi.encode()); + + if (_useResolutionAndFinality) { + _mockAndExpect(address(resolutionModule), _calldata, abi.encode()); + _mockAndExpect(address(finalityModule), _calldata, abi.encode()); + } + + // Check: emits OracleRequestFinalized event? + _expectEmit(address(oracle)); + emit OracleRequestFinalized(_requestId, bytes32(0)); + + // Test: finalize the request + vm.prank(_caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Testing the finalization of a request with multiple responses all of which have been disputed + * @dev The request might or might not use a dispute and a finality module, this is fuzzed + */ + function test_finalize_withoutResponse_withMultipleDisputedResponses( + bool _useResolutionAndFinality, + address _caller, + uint8 _status, + uint8 _numberOfResponses + ) public withoutResponse setResolutionAndFinality(_useResolutionAndFinality) { + vm.assume(_numberOfResponses < 5); + + // All responses will have the same dispute status + vm.assume(_status != uint256(IOracle.DisputeStatus.Lost)); + vm.assume(_status != uint256(IOracle.DisputeStatus.None)); + vm.assume(_status <= uint256(type(IOracle.DisputeStatus).max)); + + bytes32 _requestId = _getId(mockRequest); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + mockAccessControl.user = _caller; + + IOracle.DisputeStatus _disputeStatus = IOracle.DisputeStatus(_status); + + for (uint8 _i; _i < _numberOfResponses; _i++) { + mockResponse.response = abi.encodePacked(_i); + + // Compute the mock object ids + bytes32 _responseId = _getId(mockResponse); + mockDispute.responseId = _responseId; + bytes32 _disputeId = _getId(mockDispute); + + // The response must be disputed + oracle.mock_addResponseId(_requestId, _responseId); + oracle.mock_setDisputeOf(_responseId, _disputeId); + oracle.mock_setDisputeStatus(_disputeId, _disputeStatus); + } + + mockResponse.response = bytes(''); + + // The finalization should come through + // Check: emits OracleRequestFinalized event? + _expectEmit(address(oracle)); + emit OracleRequestFinalized(_requestId, bytes32(0)); + + // Test: finalize the request + vm.prank(_caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Finalizing an already finalized response shouldn't be possible + */ + function test_finalize_withoutResponse_revertsWhenAlreadyFinalized(address _caller) public withoutResponse { + bytes32 _requestId = _getId(mockRequest); + + // Override the finalizedAt to make it be finalized + oracle.mock_setFinalizedAt(_requestId, block.timestamp); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + + mockAccessControl.user = _caller; + + // Test: finalize a finalized request + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); + vm.prank(_caller); + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } + + /** + * @notice Finalizing a request with a non-disputed response should revert + */ + function test_finalize_withoutResponse_revertsWithNonDisputedResponse(bytes32 _responseId) + public + withoutResponse + happyPath + { + vm.assume(_responseId != bytes32(0)); + + bytes32 _requestId = _getId(mockRequest); + + // Submit a response to the request + oracle.mock_addResponseId(_requestId, _responseId); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + + // Check: reverts? + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_FinalizableResponseExists.selector, _responseId)); + + // Test: finalize the request + oracle.finalize(mockRequest, mockResponse, mockAccessControl); + } +} + +contract Oracle_Unit_EscalateDispute is BaseTest { + modifier happyPath(address _caller) { + mockAccessControl.user = _caller; + vm.startPrank(_caller); + _; + } + /** + * @notice Test if the dispute is escalated correctly and the event is emitted + */ + + function test_escalateDispute_emitsEvent(address _caller) public happyPath(_caller) { + bytes32 _disputeId = _getId(mockDispute); + + oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + + // Mock and expect the dispute module call + _mockAndExpect( + address(disputeModule), + abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), + abi.encode() + ); + + // Mock and expect the resolution module call + _mockAndExpect( + address(resolutionModule), + abi.encodeCall(IResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)), + abi.encode() + ); + + // Expect dispute escalated event + _expectEmit(address(oracle)); + emit DisputeEscalated(mockAccessControl.user, _disputeId, mockDispute); + + // Test: escalate the dispute + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // Check: The dispute has been escalated + assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); + } + + /** + * @notice Should not revert if no resolution module was configured + */ + function test_escalateDispute_noResolutionModule(address _caller) public happyPath(_caller) { + mockRequest.resolutionModule = address(0); + + bytes32 _requestId = _getId(mockRequest); + + mockResponse.requestId = _requestId; + bytes32 _responseId = _getId(mockResponse); + + mockDispute.requestId = _requestId; + mockDispute.responseId = _responseId; + bytes32 _disputeId = _getId(mockDispute); + + oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); + oracle.mock_setDisputeStatus(_disputeId, IOracle.DisputeStatus.Active); + oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + oracle.mock_setResponseCreatedAt(_responseId, block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + + // Mock and expect the dispute module call + _mockAndExpect( + address(disputeModule), + abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)), + abi.encode() + ); + + // Expect dispute escalated event + _expectEmit(address(oracle)); + emit DisputeEscalated(mockAccessControl.user, _disputeId, mockDispute); + + // Test: escalate the dispute + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // Check: The dispute has been escalated + assertEq(uint256(oracle.disputeStatus(_disputeId)), uint256(IOracle.DisputeStatus.Escalated)); + } + + /** + * /** + * @notice Revert if the dispute doesn't exist + */ + function test_escalateDispute_revertsIfInvalidDispute(address _caller) public happyPath(_caller) { + bytes32 _disputeId = _getId(mockDispute); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, 0); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); + + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + /** + * @notice Revert if the provided dispute does not match the request or the response + */ + function test_escalateDispute_revertsIfDisputeNotValid(address _caller) public happyPath(_caller) { + bytes32 _disputeId = _getId(mockDispute); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); + + // Test: escalate the dispute + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + + function test_escalateDispute_revertsIfDisputeNotActive(address _caller) public happyPath(_caller) { + bytes32 _disputeId = _getId(mockDispute); + oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setResponseCreatedAt(_getId(mockResponse), block.timestamp); + oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + oracle.mock_setDisputeOf(_getId(mockResponse), _disputeId); + + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); + + // Test: escalate the dispute + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } +} diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 79638a8..171e1ef 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import {IAccessController} from '../../contracts/AccessController.sol'; import {IOracle} from '../../contracts/Oracle.sol'; import {Test} from 'forge-std/Test.sol'; @@ -12,12 +13,14 @@ contract Helpers is Test { address public requester = makeAddr('requester'); address public proposer = makeAddr('proposer'); address public disputer = makeAddr('disputer'); + address public finalizer = makeAddr('finalizer'); // Mock objects IOracle.Request public mockRequest; IOracle.Response public mockResponse = IOracle.Response({proposer: proposer, requestId: mockId, response: bytes('')}); IOracle.Dispute public mockDispute = IOracle.Dispute({disputer: disputer, responseId: mockId, proposer: proposer, requestId: mockId}); + IAccessController.AccessControl public mockAccessControl; modifier assumeFuzzable(address _address) { _assumeFuzzable(_address); From d9ce30aa0ad57c55f43945f4c4f84580df84a4fe Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Thu, 24 Oct 2024 16:29:26 -0300 Subject: [PATCH 05/19] feat: modifier and tests --- solidity/contracts/Oracle.sol | 61 ++++++--- solidity/contracts/utils/OracleTypehash.sol | 2 - solidity/interfaces/IOracle.sol | 29 +++- .../test/integration/EscalateDispute.t.sol | 15 +- solidity/test/integration/IntegrationBase.sol | 16 ++- .../test/integration/ResponseDispute.t.sol | 11 +- .../test/integration/ResponseProposal.t.sol | 36 ++++- .../mocks/contracts/MockAtomicArbitrator.sol | 2 +- solidity/test/unit/Oracle.t.sol | 128 ++++++++++++++---- 9 files changed, 234 insertions(+), 66 deletions(-) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index dbc19a5..62931ce 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -18,7 +18,6 @@ import { _ESCALATE_TYPEHASH, _FINALIZE_TYPEHASH, _PROPOSE_TYPEHASH, - _RESOLVE_TYPEHASH, _UPDATE_TYPEHASH } from './utils/OracleTypehash.sol'; @@ -66,6 +65,18 @@ contract Oracle is IOracle, AccessController { */ mapping(bytes32 _requestId => bytes _responseIds) internal _responseIds; + /** + * @notice Modifier to check if the user approved to the access control module + * @param _user The address of the user + * @param _accessControlModule The access control module to check if approved + */ + modifier isApproved(address _user, address _accessControlModule) { + if (_accessControlModule != address(0) && !isAccessControlApproved[_user][_accessControlModule]) { + revert Oracle_AccessControlModuleNotApproved(); + } + _; + } + /// @inheritdoc IOracle function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) { bytes memory _responses = _responseIds[_requestId]; @@ -107,6 +118,13 @@ contract Oracle is IOracle, AccessController { } } + /// @inheritdoc IOracle + function setAccessControlModule(address _accessControlModule, bool _approved) external { + isAccessControlApproved[msg.sender][_accessControlModule] = _approved; + + emit AccessControlModuleSet(msg.sender, _accessControlModule, _approved); + } + /// @inheritdoc IOracle function createRequest( Request calldata _request, @@ -125,7 +143,7 @@ contract Oracle is IOracle, AccessController { uint256 _requestsAmount = _requestsData.length; _batchRequestsIds = new bytes32[](_requestsAmount); - for (uint256 _i = 0; _i < _requestsAmount;) { + for (uint256 _i; _i < _requestsAmount;) { _batchRequestsIds[_i] = _createRequest(_requestsData[_i], _ipfsHashes[_i], _accessControl[_i]); unchecked { ++_i; @@ -140,6 +158,7 @@ contract Oracle is IOracle, AccessController { AccessControl calldata _accessControl ) external + isApproved(_accessControl.user, _request.accessControlModule) hasAccess(_request.accessControlModule, _PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) returns (bytes32 _responseId) { @@ -180,6 +199,7 @@ contract Oracle is IOracle, AccessController { AccessControl calldata _accessControl ) external + isApproved(_accessControl.user, _request.accessControlModule) hasAccess(_request.accessControlModule, _DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) returns (bytes32 _disputeId) { @@ -225,6 +245,7 @@ contract Oracle is IOracle, AccessController { AccessControl calldata _accessControl ) external + isApproved(_accessControl.user, _request.accessControlModule) hasAccess(_request.accessControlModule, _ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -256,15 +277,7 @@ contract Oracle is IOracle, AccessController { } /// @inheritdoc IOracle - function resolveDispute( - Request calldata _request, - Response calldata _response, - Dispute calldata _dispute, - AccessControl calldata _accessControl - ) - external - hasAccess(_request.accessControlModule, _RESOLVE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) - { + function resolveDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); if (disputeCreatedAt[_disputeId] == 0) { @@ -292,13 +305,14 @@ contract Oracle is IOracle, AccessController { /// @inheritdoc IOracle function updateDisputeStatus( - Request calldata _request, - Response calldata _response, - Dispute calldata _dispute, + Request memory _request, + Response memory _response, + Dispute memory _dispute, DisputeStatus _status, AccessControl calldata _accessControl ) external + isApproved(_accessControl.user, _request.accessControlModule) hasAccess( _request.accessControlModule, _UPDATE_TYPEHASH, @@ -316,16 +330,24 @@ contract Oracle is IOracle, AccessController { revert Oracle_InvalidDisputeId(_disputeId); } + // Needed to avoid stack too deep when try to compile + Request memory _currentRequest = _request; + Response memory _currentResponse = _response; + Dispute memory _currentDispute = _dispute; + DisputeStatus _currentStatus = _status; + if ( - _accessControl.user != address(_request.disputeModule) - && _accessControl.user != address(_request.resolutionModule) + _accessControl.user != address(_currentRequest.disputeModule) + && _accessControl.user != address(_currentRequest.resolutionModule) ) { revert Oracle_NotDisputeOrResolutionModule(_accessControl.user); } - disputeStatus[_disputeId] = _status; - IDisputeModule(_request.disputeModule).onDisputeStatusChange(_disputeId, _request, _response, _dispute); + disputeStatus[_disputeId] = _currentStatus; + IDisputeModule(_currentRequest.disputeModule).onDisputeStatusChange( + _disputeId, _currentRequest, _currentResponse, _currentDispute + ); - emit DisputeStatusUpdated(_disputeId, _dispute, _status); + emit DisputeStatusUpdated(_disputeId, _currentDispute, _currentStatus); } /// @inheritdoc IOracle @@ -445,6 +467,7 @@ contract Oracle is IOracle, AccessController { AccessControl calldata _accessControl ) internal + isApproved(_accessControl.user, _request.accessControlModule) hasAccess(_request.accessControlModule, _CREATE_TYPEHASH, abi.encode(_request), _accessControl) returns (bytes32 _requestId) { diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index 2a7029e..dea1760 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -10,8 +10,6 @@ bytes32 constant _DISPUTE_TYPEHASH = keccak256('DisputeResponse(Request _request bytes32 constant _ESCALATE_TYPEHASH = keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); -bytes32 constant _RESOLVE_TYPEHASH = keccak256('ResolveDispute(Request _request,Response _response,Dispute _dispute)'); - bytes32 constant _UPDATE_TYPEHASH = keccak256('UpdateDisputeStatus(Request _request,Response _response,Dispute _dispute,DisputeStatus _status)'); diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 0f4672f..9d47fad 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -12,6 +12,14 @@ interface IOracle is IAccessController { EVENTS //////////////////////////////////////////////////////////////*/ + /** + * @notice Emitted when the access control module is set + * @param _user The address of the user + * @param _accessControlModule The address of the access control module + * @param _approved If the module is approved + */ + event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); + /** * @notice Emitted when a request is created * @param _requestId The id of the created request @@ -70,6 +78,11 @@ interface IOracle is IAccessController { ERRORS //////////////////////////////////////////////////////////////*/ + /** + * @notice Thrown when user didn't approve the access control module + */ + error Oracle_AccessControlModuleNotApproved(); + /** * @notice Thrown when an unauthorized caller is trying to change a dispute's status * @param _caller The caller of the function @@ -370,6 +383,14 @@ interface IOracle is IAccessController { LOGIC //////////////////////////////////////////////////////////////*/ + /** + * @notice Sets the address of the access control module + * + * @param _accessControlModule The address of the access control module + * @param _approved If the module is approved + */ + function setAccessControlModule(address _accessControlModule, bool _approved) external; + /** * @notice Generates the request ID and initializes the modules for the request * @@ -450,14 +471,8 @@ interface IOracle is IAccessController { * @param _request The request * @param _response The disputed response * @param _dispute The dispute that is being resolved - * @param _accessControl The access control data */ - function resolveDispute( - Request calldata _request, - Response calldata _response, - Dispute calldata _dispute, - AccessControl calldata _accessControl - ) external; + function resolveDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external; /** * @notice Updates the status of a dispute diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index 83d317f..62e3ecb 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -14,12 +14,19 @@ contract Integration_EscalateDispute is IntegrationBase { oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); vm.stopPrank(); - // Dispute reverts if caller is not authorized - vm.startPrank(badCaller); - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + // Revert if not approved to dispute + address _badDisputer = makeAddr('badDisputer'); + mockAccessControl.user = _badDisputer; + + // Reverts because caller is not approved to dispute + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // Dispute reverts if caller is not approved mockAccessControl.user = disputer; + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.prank(badCaller); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); - vm.stopPrank(); // Dispute the response vm.startPrank(caller); diff --git a/solidity/test/integration/IntegrationBase.sol b/solidity/test/integration/IntegrationBase.sol index f15ccfe..4aedb45 100644 --- a/solidity/test/integration/IntegrationBase.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -149,9 +149,10 @@ contract IntegrationBase is TestConstants, Helpers { mockDispute.responseId = _getId(mockResponse); // Add the allowed callers to the access control module - address[] memory _allowedCallers = new address[](1); - _allowedCallers[0] = caller; - _accessControlModule.setHasAccess(_allowedCallers); + _setAccessControlForACaller(requester, caller); + _setAccessControlForACaller(proposer, caller); + _setAccessControlForACaller(disputer, caller); + _setAccessControlForACaller(finalizer, caller); vm.startPrank(caller); } @@ -164,4 +165,13 @@ contract IntegrationBase is TestConstants, Helpers { vm.warp(block.timestamp + _blocks * BLOCK_TIME); vm.roll(block.number + _blocks); } + + function _setAccessControlForACaller(address _delegator, address _caller) internal { + vm.startPrank(_delegator); + address[] memory _allowedCallers = new address[](1); + _allowedCallers[0] = _caller; + _accessControlModule.setHasAccess(_allowedCallers); + oracle.setAccessControlModule(address(_accessControlModule), true); + vm.stopPrank(); + } } diff --git a/solidity/test/integration/ResponseDispute.t.sol b/solidity/test/integration/ResponseDispute.t.sol index 3b815d7..c2cab00 100644 --- a/solidity/test/integration/ResponseDispute.t.sol +++ b/solidity/test/integration/ResponseDispute.t.sol @@ -30,19 +30,28 @@ contract Integration_ResponseDispute is IntegrationBase { mockAccessControl.user = finalizer; oracle.finalize(mockRequest, mockResponse, mockAccessControl); + // Revert if the dispute is already finalized mockAccessControl.user = disputer; vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); } function test_disputeResponse_alreadyDisputed() public { + // Dispute the response mockAccessControl.user = disputer; oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); address _anotherDisputer = makeAddr('anotherDisputer'); mockDispute.disputer = _anotherDisputer; - mockAccessControl.user = _anotherDisputer; + + // Revert if the response is access control is not approved + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + + // Revert if the response is already disputed + mockAccessControl.user = disputer; + mockDispute.disputer = disputer; vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_ResponseAlreadyDisputed.selector, _responseId)); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); } diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index 0467de9..e9d1b43 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -8,16 +8,25 @@ contract Integration_ResponseProposal is IntegrationBase { function setUp() public override { super.setUp(); - vm.stopPrank(); mockRequest.nonce = uint96(oracle.totalRequestCount()); - mockAccessControl.user = requester; - vm.startPrank(badCaller); - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + address _badRequester = makeAddr('badRequester'); + mockAccessControl.user = _badRequester; + + // Revert if not approved to create request + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + vm.stopPrank(); + // Revert if has no access to create request + mockAccessControl.user = requester; + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.prank(badCaller); + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + // Create request vm.startPrank(caller); _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); } @@ -25,6 +34,22 @@ contract Integration_ResponseProposal is IntegrationBase { function test_proposeResponse_validResponse(bytes memory _response) public { mockResponse.response = _response; + // Revert if not approved to create request + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + address _badProposer = makeAddr('badProposer'); + mockAccessControl.user = _badProposer; + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + vm.stopPrank(); + + // Revert if has no access to create request + mockAccessControl.user = proposer; + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.prank(badCaller); + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + + vm.startPrank(caller); + // Propose response mockAccessControl.user = proposer; oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); @@ -38,13 +63,16 @@ contract Integration_ResponseProposal is IntegrationBase { function test_proposeResponse_finalizedRequest(uint256 _timestamp) public { _timestamp = bound(_timestamp, _expectedDeadline + _baseDisputeWindow, type(uint128).max); + // Propose response mockAccessControl.user = proposer; oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + // Finalize request vm.warp(_timestamp); mockAccessControl.user = finalizer; oracle.finalize(mockRequest, mockResponse, mockAccessControl); + // Revert if the request is already finalized mockAccessControl.user = proposer; mockResponse.response = abi.encode(_timestamp); vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); diff --git a/solidity/test/mocks/contracts/MockAtomicArbitrator.sol b/solidity/test/mocks/contracts/MockAtomicArbitrator.sol index b7af1a2..a807c10 100644 --- a/solidity/test/mocks/contracts/MockAtomicArbitrator.sol +++ b/solidity/test/mocks/contracts/MockAtomicArbitrator.sol @@ -18,7 +18,7 @@ contract MockAtomicArbitrator { ) external returns (bytes memory _result) { _result = new bytes(0); answer = IOracle.DisputeStatus.Won; - // oracle.resolveDispute(_request, _response, _dispute); + oracle.resolveDispute(_request, _response, _dispute); } function getAnswer(bytes32 /* _dispute */ ) external view returns (IOracle.DisputeStatus _answer) { diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 2d37deb..e3ddee4 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -73,6 +73,10 @@ contract MockOracle is Oracle { function mock_addResponseId(bytes32 _requestId, bytes32 _responseId) external { _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); } + + function mock_setAccessControlApproved(address _user, address _accessControlModule, bool _approved) external { + isAccessControlApproved[_user][_accessControlModule] = _approved; + } } /** @@ -101,6 +105,7 @@ contract BaseTest is Test, Helpers { event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute); + event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); function setUp() public virtual { oracle = new MockOracle(); @@ -129,10 +134,28 @@ contract BaseTest is Test, Helpers { } } +contract Oracle_Unit_SetAccessControlModule is BaseTest { + /** + * @notice Test the access control module setter + */ + function test_setAccessControlModuleTrue(bool _approved) public { + // Check: emits AccessControlModuleSet event? + _expectEmit(address(oracle)); + emit AccessControlModuleSet(address(this), address(accessControlModule), _approved); + + // Test: set the access control module + oracle.setAccessControlModule(address(accessControlModule), _approved); + + // Check: correct access control module set? + assertEq(oracle.isAccessControlApproved(address(this), address(accessControlModule)), _approved); + } +} + contract Oracle_Unit_CreateRequest is BaseTest { modifier happyPath() { mockAccessControl.user = requester; vm.startPrank(requester); + oracle.mock_setAccessControlApproved(requester, address(accessControlModule), true); _; } /** @@ -195,6 +218,34 @@ contract Oracle_Unit_CreateRequest is BaseTest { assertEq(oracle.totalRequestCount(), _initialNonce + 1); } + /** + * @notice Check that creating a request with a non-approved access control module reverts + */ + function test_createRequest_revertsIfNotApproved() public { + // Check: revert? + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + + // Test: try to create the request + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + } + + /** + * @notice Check that reverts if the access control module returns false + */ + function test_createRequest_revertsIfInvalidAccessControlData(address _caller) public { + vm.assume(_caller != requester); + + mockRequest.accessControlModule = address(0); + mockAccessControl.user = requester; + + // Check: revert? + vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + + // Test: try to create the request + vm.prank(_caller); + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + } + /** * @notice Check that creating a request with a nonce that already exists reverts */ @@ -245,6 +296,9 @@ contract Oracle_Unit_CreateRequests is BaseTest { bytes32[] memory _ipfsHashes = new bytes32[](_requestsAmount); IAccessController.AccessControl[] memory _accessControls = new IAccessController.AccessControl[](_requestsAmount); + vm.startPrank(requester); + oracle.mock_setAccessControlApproved(requester, address(accessControlModule), true); + // Generate requests batch for (uint256 _i = 0; _i < _requestsAmount; _i++) { mockRequest.requestModuleData = _requestData; @@ -265,7 +319,6 @@ contract Oracle_Unit_CreateRequests is BaseTest { emit RequestCreated(_theoreticalRequestId, mockRequest, _ipfsHashes[_i]); } - vm.prank(requester); bytes32[] memory _requestsIds = oracle.createRequests(_requests, _ipfsHashes, _accessControls); for (uint256 _i = 0; _i < _requestsIds.length; _i++) { @@ -327,7 +380,8 @@ contract Oracle_Unit_CreateRequests is BaseTest { _accessControls[_i].user = requester; } - vm.prank(requester); + vm.startPrank(requester); + oracle.mock_setAccessControlApproved(requester, address(accessControlModule), true); oracle.createRequests(_requests, _ipfsHashes, _accessControls); uint256 _newNonce = oracle.totalRequestCount(); @@ -416,6 +470,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { modifier happyPath() { mockAccessControl.user = proposer; vm.startPrank(proposer); + oracle.mock_setAccessControlApproved(proposer, address(accessControlModule), true); _; } /** @@ -469,6 +524,17 @@ contract Oracle_Unit_ProposeResponse is BaseTest { assertEq(_responseIds[1], _secondResponseId); } + /** + * @notice Check that proposing a response with a non-approved access control module reverts + */ + function test_proposeResponse_revertsIfNotApproved() public { + // Check: revert? + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + + // Test: try to create the request + oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); + } + function test_proposeResponse_revertsIfInvalidRequest() public happyPath { // Check: revert? vm.expectRevert(IOracle.Oracle_InvalidRequest.selector); @@ -502,6 +568,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); + oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); mockAccessControl.user = _caller; // Check: revert? @@ -562,6 +629,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { modifier happyPath() { mockAccessControl.user = disputer; vm.startPrank(disputer); + oracle.mock_setAccessControlApproved(disputer, address(accessControlModule), true); _; } @@ -594,6 +662,17 @@ contract Oracle_Unit_DisputeResponse is BaseTest { } } + /** + * @notice Check that dispute a response with a non-approved access control module reverts + */ + function test_disputeResponse_revertsIfNotApproved() public { + // Check: revert? + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + + // Test: try to create the request + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + /** * @notice Reverts if the dispute proposer and response proposer are not same */ @@ -645,6 +724,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { function test_disputeResponse_revertIfWrongDisputer(address _caller) public { vm.assume(_caller != disputer); + oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); mockAccessControl.user = _caller; // Check: revert? @@ -672,6 +752,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { modifier happyPath() { mockAccessControl.user = address(disputeModule); vm.startPrank(address(disputeModule)); + oracle.mock_setAccessControlApproved(address(disputeModule), address(accessControlModule), true); _; } /** @@ -716,6 +797,17 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { } } + /** + * @notice Check that update dispute status with a non-approved access control module reverts + */ + function test_updateDisputeStatus_revertsIfNotApproved() public { + // Check: revert? + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + + // Test: try to create the request + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active, mockAccessControl); + } + /** * @notice Providing a dispute that does not match the response should revert */ @@ -746,6 +838,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { mockRequest.accessControlModule = address(0); mockAccessControl.user = address(disputeModule); + oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); // Check: revert? vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); @@ -768,6 +861,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { // Mock the dispute oracle.mock_setDisputeOf(_responseId, _disputeId); oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); + oracle.mock_setAccessControlApproved(proposer, address(accessControlModule), true); mockAccessControl.user = proposer; vm.mockCall( @@ -793,6 +887,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { mockAccessControl.user = address(resolutionModule); oracle.mock_setDisputeCreatedAt(_disputeId, 0); + oracle.mock_setAccessControlApproved(address(resolutionModule), address(accessControlModule), true); // Check: revert? vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); @@ -805,7 +900,6 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { contract Oracle_Unit_ResolveDispute is BaseTest { modifier happyPath() { - mockAccessControl.user = address(resolutionModule); vm.startPrank(address(resolutionModule)); _; } @@ -835,24 +929,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { emit DisputeResolved(_disputeId, mockDispute); // Test: resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); - } - - /** - * @notice Revert if the access control module returns false - */ - function test_resolveDispute_revertsIfInvalidAccessControlData(address _caller) public { - vm.assume(_caller != address(resolutionModule)); - - mockRequest.accessControlModule = address(0); - mockAccessControl.user = address(resolutionModule); - - // Check: revert? - vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); - - // Test: try to propose a response from a random address - vm.prank(_caller); - oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute); } /** @@ -865,7 +942,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _getId(mockDispute))); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute); } /** @@ -878,7 +955,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute); } /** @@ -905,7 +982,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotResolve.selector, _disputeId)); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute); } } @@ -935,7 +1012,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NoResolutionModule.selector, _disputeId)); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute); } } @@ -1276,6 +1353,7 @@ contract Oracle_Unit_EscalateDispute is BaseTest { modifier happyPath(address _caller) { mockAccessControl.user = _caller; vm.startPrank(_caller); + oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); _; } /** From 6ae19ceda39dc2b938c33e3fec208a739430a6dc Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Mon, 28 Oct 2024 14:56:42 -0300 Subject: [PATCH 06/19] feat: encode call --- solidity/contracts/AccessController.sol | 18 ++++--- solidity/contracts/Oracle.sol | 26 +++------ solidity/contracts/utils/OracleTypehash.sol | 3 -- solidity/interfaces/IOracle.sol | 4 +- .../accessControl/IAccessControlModule.sol | 35 ++++++++---- .../contracts/MockAccessControlModule.sol | 22 ++++---- .../interfaces/IMockAccessControlModule.sol | 7 ++- solidity/test/unit/Oracle.t.sol | 54 ++++--------------- 8 files changed, 72 insertions(+), 97 deletions(-) diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol index 100a03a..fe9f367 100644 --- a/solidity/contracts/AccessController.sol +++ b/solidity/contracts/AccessController.sol @@ -19,13 +19,17 @@ abstract contract AccessController is IAccessController { bool _hasAccess = msg.sender == _accessControl.user || ( _accessControlModule != address(0) - && IAccessControlModule(_accessControlModule).hasAccess({ - _caller: msg.sender, - _user: _accessControl.user, - _typehash: _typehash, - _params: _params, - _data: _accessControl.data - }) + && IAccessControlModule(_accessControlModule).hasAccess( + abi.encode( + IAccessControlModule.AccessControlParameters({ + sender: msg.sender, + user: _accessControl.user, + typehash: _typehash, + data: _accessControl.data, + params: _params + }) + ) + ) ); if (!_hasAccess) revert AccessControlData_NoAccess(); _; diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 62931ce..76e7b5e 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -17,8 +17,7 @@ import { _DISPUTE_TYPEHASH, _ESCALATE_TYPEHASH, _FINALIZE_TYPEHASH, - _PROPOSE_TYPEHASH, - _UPDATE_TYPEHASH + _PROPOSE_TYPEHASH } from './utils/OracleTypehash.sol'; contract Oracle is IOracle, AccessController { @@ -308,18 +307,8 @@ contract Oracle is IOracle, AccessController { Request memory _request, Response memory _response, Dispute memory _dispute, - DisputeStatus _status, - AccessControl calldata _accessControl - ) - external - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess( - _request.accessControlModule, - _UPDATE_TYPEHASH, - abi.encode(_request, _response, _dispute, _status), - _accessControl - ) - { + DisputeStatus _status + ) external { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); if (disputeCreatedAt[_disputeId] == 0) { @@ -336,11 +325,9 @@ contract Oracle is IOracle, AccessController { Dispute memory _currentDispute = _dispute; DisputeStatus _currentStatus = _status; - if ( - _accessControl.user != address(_currentRequest.disputeModule) - && _accessControl.user != address(_currentRequest.resolutionModule) - ) { - revert Oracle_NotDisputeOrResolutionModule(_accessControl.user); + if (msg.sender != address(_currentRequest.disputeModule) && msg.sender != address(_currentRequest.resolutionModule)) + { + revert Oracle_NotDisputeOrResolutionModule(msg.sender); } disputeStatus[_disputeId] = _currentStatus; IDisputeModule(_currentRequest.disputeModule).onDisputeStatusChange( @@ -357,6 +344,7 @@ contract Oracle is IOracle, AccessController { AccessControl calldata _accessControl ) external + isApproved(_accessControl.user, _request.accessControlModule) hasAccess(_request.accessControlModule, _FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) { bytes32 _requestId; diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index dea1760..d3b0532 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -10,7 +10,4 @@ bytes32 constant _DISPUTE_TYPEHASH = keccak256('DisputeResponse(Request _request bytes32 constant _ESCALATE_TYPEHASH = keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); -bytes32 constant _UPDATE_TYPEHASH = - keccak256('UpdateDisputeStatus(Request _request,Response _response,Dispute _dispute,DisputeStatus _status)'); - bytes32 constant _FINALIZE_TYPEHASH = keccak256('Finalize(Request _request,Response _response)'); diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 9d47fad..3b8722b 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -481,14 +481,12 @@ interface IOracle is IAccessController { * @param _response The disputed response * @param _dispute The dispute that is being updated * @param _status The new status of the dispute - * @param _accessControl The access control data */ function updateDisputeStatus( Request calldata _request, Response calldata _response, Dispute calldata _dispute, - DisputeStatus _status, - AccessControl calldata _accessControl + DisputeStatus _status ) external; /** diff --git a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol index 1868425..2f0c27e 100644 --- a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol +++ b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol @@ -8,20 +8,33 @@ import {IModule} from '../../IModule.sol'; * @notice Common interface for all response modules */ interface IAccessControlModule is IModule { + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Access control parameters + * @param sender The address of the sender + * @param user The address of the user + * @param typehash The typehash of the access control + * @param data The data for access control validation + * @param params The parameters for access control validation + */ + struct AccessControlParameters { + address sender; + address user; + bytes32 typehash; + bytes data; + bytes params; + } + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ /** * @notice Checks if the caller has access to the user - * @param _caller The caller address - * @param _user The user address - * @param _typehash The typehash of the request - * @param _params The parameters of the request * @param _data The data for access control validation * @return _hasAccess True if the caller has access to the user */ - function hasAccess( - address _caller, - address _user, - bytes32 _typehash, - bytes memory _params, - bytes calldata _data - ) external returns (bool _hasAccess); + function hasAccess(bytes calldata _data) external returns (bool _hasAccess); } diff --git a/solidity/test/mocks/contracts/MockAccessControlModule.sol b/solidity/test/mocks/contracts/MockAccessControlModule.sol index 5069614..94dd219 100644 --- a/solidity/test/mocks/contracts/MockAccessControlModule.sol +++ b/solidity/test/mocks/contracts/MockAccessControlModule.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.19; import {Module} from '../../../contracts/Module.sol'; import {IOracle} from '../../../interfaces/IOracle.sol'; -import {IMockAccessControlModule} from '../interfaces/IMockAccessControlModule.sol'; +import {IAccessControlModule, IMockAccessControlModule} from '../interfaces/IMockAccessControlModule.sol'; contract MockAccessControlModule is Module, IMockAccessControlModule { mapping(address _caller => bool _hasAccess) public callerHasAccess; @@ -19,13 +19,17 @@ contract MockAccessControlModule is Module, IMockAccessControlModule { function moduleName() external view returns (string memory _moduleName) {} - function hasAccess( - address _caller, - address, - bytes32, - bytes memory, - bytes memory - ) external view override returns (bool) { - return callerHasAccess[_caller]; + function hasAccess(bytes memory _data) external view override returns (bool _hasAccess) { + IAccessControlModule.AccessControlParameters memory _accessControlData = decodeAccesControlData(_data); + _hasAccess = callerHasAccess[_accessControlData.sender]; + } + + function decodeAccesControlData(bytes memory _data) + public + pure + override + returns (IAccessControlModule.AccessControlParameters memory _accessControlData) + { + _accessControlData = abi.decode(_data, (IAccessControlModule.AccessControlParameters)); } } diff --git a/solidity/test/mocks/interfaces/IMockAccessControlModule.sol b/solidity/test/mocks/interfaces/IMockAccessControlModule.sol index bd4ee2d..fb59a5c 100644 --- a/solidity/test/mocks/interfaces/IMockAccessControlModule.sol +++ b/solidity/test/mocks/interfaces/IMockAccessControlModule.sol @@ -3,4 +3,9 @@ pragma solidity ^0.8.19; import {IAccessControlModule} from '../../../interfaces/modules/accessControl/IAccessControlModule.sol'; -interface IMockAccessControlModule is IAccessControlModule {} +interface IMockAccessControlModule is IAccessControlModule { + function decodeAccesControlData(bytes calldata _data) + external + view + returns (AccessControlParameters memory _accessControlData); +} diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index e3ddee4..0eddb24 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -750,9 +750,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { contract Oracle_Unit_UpdateDisputeStatus is BaseTest { modifier happyPath() { - mockAccessControl.user = address(disputeModule); vm.startPrank(address(disputeModule)); - oracle.mock_setAccessControlApproved(address(disputeModule), address(accessControlModule), true); _; } /** @@ -787,9 +785,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { emit DisputeStatusUpdated(_disputeId, mockDispute, IOracle.DisputeStatus(_newStatus)); // Test: change the status - oracle.updateDisputeStatus( - mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus), mockAccessControl - ); + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); // Check: correct status stored? assertEq(_newStatus, uint256(oracle.disputeStatus(_disputeId))); @@ -797,17 +793,6 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { } } - /** - * @notice Check that update dispute status with a non-approved access control module reverts - */ - function test_updateDisputeStatus_revertsIfNotApproved() public { - // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); - - // Test: try to create the request - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active, mockAccessControl); - } - /** * @notice Providing a dispute that does not match the response should revert */ @@ -825,27 +810,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _disputeId)); // Test: Try to update the dispute - oracle.updateDisputeStatus( - mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus), mockAccessControl - ); - } - - /** - * @notice Revert if the access control module returns false - */ - function test_updateDisputeStatus_revertsIfInvalidAccessControlData(address _caller) public { - vm.assume(_caller != address(disputeModule)); - - mockRequest.accessControlModule = address(0); - mockAccessControl.user = address(disputeModule); - oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); - - // Check: revert? - vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); - - // Test: try to propose a response from a random address - vm.prank(_caller); - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active, mockAccessControl); + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); } /** @@ -863,7 +828,6 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); oracle.mock_setAccessControlApproved(proposer, address(accessControlModule), true); - mockAccessControl.user = proposer; vm.mockCall( address(accessControlModule), abi.encodeWithSelector(IAccessControlModule.hasAccess.selector), abi.encode(true) ); @@ -873,9 +837,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { // Test: try to update the status from an EOA vm.prank(proposer); - oracle.updateDisputeStatus( - mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus), mockAccessControl - ); + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus(_newStatus)); } /** @@ -884,8 +846,6 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { function test_updateDisputeStatus_revertsIfInvalidDispute() public { bytes32 _disputeId = _getId(mockDispute); - mockAccessControl.user = address(resolutionModule); - oracle.mock_setDisputeCreatedAt(_disputeId, 0); oracle.mock_setAccessControlApproved(address(resolutionModule), address(accessControlModule), true); @@ -894,7 +854,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { // Test: try to update the status vm.prank(address(resolutionModule)); - oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active, mockAccessControl); + oracle.updateDisputeStatus(mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Active); } } @@ -1077,6 +1037,7 @@ contract Oracle_Unit_Finalize is BaseTest { modifier happyPath() { mockAccessControl.user = address(requester); + oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); vm.startPrank(address(requester)); _; } @@ -1094,6 +1055,7 @@ contract Oracle_Unit_Finalize is BaseTest { bytes32 _responseId = _getId(mockResponse); mockAccessControl.user = _caller; + oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); oracle.mock_addResponseId(_requestId, _responseId); oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); @@ -1236,6 +1198,7 @@ contract Oracle_Unit_Finalize is BaseTest { oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); mockResponse.requestId = bytes32(0); mockAccessControl.user = _caller; + oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); // Create mock request and store it bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); @@ -1277,7 +1240,9 @@ contract Oracle_Unit_Finalize is BaseTest { bytes32 _requestId = _getId(mockRequest); oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); + mockAccessControl.user = _caller; + oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); IOracle.DisputeStatus _disputeStatus = IOracle.DisputeStatus(_status); @@ -1318,6 +1283,7 @@ contract Oracle_Unit_Finalize is BaseTest { oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); mockAccessControl.user = _caller; + oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); // Test: finalize a finalized request vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); From 5852f9004590bd3e158b5d1b8a077446715356bd Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Mon, 28 Oct 2024 15:51:30 -0300 Subject: [PATCH 07/19] fix: move to contract to export --- solidity/contracts/Oracle.sol | 38 ++++++--------------- solidity/contracts/utils/OracleTypehash.sol | 16 +++++---- 2 files changed, 21 insertions(+), 33 deletions(-) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 76e7b5e..ba713ac 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -11,16 +11,9 @@ import {IResolutionModule} from '../interfaces/modules/resolution/IResolutionMod import {IResponseModule} from '../interfaces/modules/response/IResponseModule.sol'; import {ValidatorLib} from '../libraries/ValidatorLib.sol'; import {AccessController} from './AccessController.sol'; +import {OracleTypehash} from './utils/OracleTypehash.sol'; -import { - _CREATE_TYPEHASH, - _DISPUTE_TYPEHASH, - _ESCALATE_TYPEHASH, - _FINALIZE_TYPEHASH, - _PROPOSE_TYPEHASH -} from './utils/OracleTypehash.sol'; - -contract Oracle is IOracle, AccessController { +contract Oracle is IOracle, AccessController, OracleTypehash { using ValidatorLib for *; /// @inheritdoc IOracle @@ -158,7 +151,7 @@ contract Oracle is IOracle, AccessController { ) external isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, _PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) + hasAccess(_request.accessControlModule, PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) returns (bytes32 _responseId) { _responseId = ValidatorLib._validateResponse(_request, _response); @@ -199,7 +192,7 @@ contract Oracle is IOracle, AccessController { ) external isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, _DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + hasAccess(_request.accessControlModule, DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) returns (bytes32 _disputeId) { bytes32 _responseId; @@ -245,7 +238,7 @@ contract Oracle is IOracle, AccessController { ) external isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, _ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + hasAccess(_request.accessControlModule, ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -319,22 +312,13 @@ contract Oracle is IOracle, AccessController { revert Oracle_InvalidDisputeId(_disputeId); } - // Needed to avoid stack too deep when try to compile - Request memory _currentRequest = _request; - Response memory _currentResponse = _response; - Dispute memory _currentDispute = _dispute; - DisputeStatus _currentStatus = _status; - - if (msg.sender != address(_currentRequest.disputeModule) && msg.sender != address(_currentRequest.resolutionModule)) - { + if (msg.sender != address(_request.disputeModule) && msg.sender != address(_request.resolutionModule)) { revert Oracle_NotDisputeOrResolutionModule(msg.sender); } - disputeStatus[_disputeId] = _currentStatus; - IDisputeModule(_currentRequest.disputeModule).onDisputeStatusChange( - _disputeId, _currentRequest, _currentResponse, _currentDispute - ); + disputeStatus[_disputeId] = _status; + IDisputeModule(_request.disputeModule).onDisputeStatusChange(_disputeId, _request, _response, _dispute); - emit DisputeStatusUpdated(_disputeId, _currentDispute, _currentStatus); + emit DisputeStatusUpdated(_disputeId, _dispute, _status); } /// @inheritdoc IOracle @@ -345,7 +329,7 @@ contract Oracle is IOracle, AccessController { ) external isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, _FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) + hasAccess(_request.accessControlModule, FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) { bytes32 _requestId; bytes32 _responseId; @@ -456,7 +440,7 @@ contract Oracle is IOracle, AccessController { ) internal isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, _CREATE_TYPEHASH, abi.encode(_request), _accessControl) + hasAccess(_request.accessControlModule, CREATE_TYPEHASH, abi.encode(_request), _accessControl) returns (bytes32 _requestId) { uint256 _requestNonce = totalRequestCount++; diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index d3b0532..b1e8bf7 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -1,13 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -bytes32 constant _CREATE_TYPEHASH = - keccak256('CreateRequest(Request _request,bytes32 _ipfsHash,AccessControl _accessControl'); +contract OracleTypehash { + bytes32 public constant CREATE_TYPEHASH = + keccak256('CreateRequest(Request _request,bytes32 _ipfsHash,AccessControl _accessControl'); -bytes32 constant _PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request,Response _response)'); + bytes32 public constant PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request,Response _response)'); -bytes32 constant _DISPUTE_TYPEHASH = keccak256('DisputeResponse(Request _request,Response _response,Dispute _dispute)'); + bytes32 public constant DISPUTE_TYPEHASH = + keccak256('DisputeResponse(Request _request,Response _response,Dispute _dispute)'); -bytes32 constant _ESCALATE_TYPEHASH = keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); + bytes32 public constant ESCALATE_TYPEHASH = + keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); -bytes32 constant _FINALIZE_TYPEHASH = keccak256('Finalize(Request _request,Response _response)'); + bytes32 public constant FINALIZE_TYPEHASH = keccak256('Finalize(Request _request,Response _response)'); +} From c4d3d48f2e33e8f46b0533c6f6043a3fd09881bf Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Thu, 31 Oct 2024 19:38:04 -0300 Subject: [PATCH 08/19] feat: comments --- solidity/contracts/AccessController.sol | 3 +- solidity/contracts/Oracle.sol | 12 +- .../accessControl/IAccessControlModule.sol | 4 +- solidity/test/unit/AccessController.t.sol | 127 ++++++++++++++++++ 4 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 solidity/test/unit/AccessController.t.sol diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol index fe9f367..c91896d 100644 --- a/solidity/contracts/AccessController.sol +++ b/solidity/contracts/AccessController.sol @@ -23,9 +23,8 @@ abstract contract AccessController is IAccessController { abi.encode( IAccessControlModule.AccessControlParameters({ sender: msg.sender, - user: _accessControl.user, + accessControl: _accessControl, typehash: _typehash, - data: _accessControl.data, params: _params }) ) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index ba713ac..fca7dcb 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -163,7 +163,7 @@ contract Oracle is IOracle, AccessController, OracleTypehash { } // The caller must be the proposer, unless the response is coming from a dispute module - if (_accessControl.user != _response.proposer && _accessControl.user != address(_request.disputeModule)) { + if (_accessControl.user != _response.proposer && _accessControl.user != _request.disputeModule) { revert Oracle_InvalidProposer(); } @@ -262,7 +262,7 @@ contract Oracle is IOracle, AccessController, OracleTypehash { emit DisputeEscalated(_accessControl.user, _disputeId, _dispute); - if (address(_request.resolutionModule) != address(0)) { + if (_request.resolutionModule != address(0)) { // Initiate the resolution IResolutionModule(_request.resolutionModule).startResolution(_disputeId, _request, _response, _dispute); } @@ -286,7 +286,7 @@ contract Oracle is IOracle, AccessController, OracleTypehash { revert Oracle_CannotResolve(_disputeId); } - if (address(_request.resolutionModule) == address(0)) { + if (_request.resolutionModule == address(0)) { revert Oracle_NoResolutionModule(_disputeId); } @@ -312,7 +312,7 @@ contract Oracle is IOracle, AccessController, OracleTypehash { revert Oracle_InvalidDisputeId(_disputeId); } - if (msg.sender != address(_request.disputeModule) && msg.sender != address(_request.resolutionModule)) { + if (msg.sender != _request.disputeModule && msg.sender != _request.resolutionModule) { revert Oracle_NotDisputeOrResolutionModule(msg.sender); } disputeStatus[_disputeId] = _status; @@ -347,11 +347,11 @@ contract Oracle is IOracle, AccessController, OracleTypehash { finalizedAt[_requestId] = block.timestamp; - if (address(_request.finalityModule) != address(0)) { + if (_request.finalityModule != address(0)) { IFinalityModule(_request.finalityModule).finalizeRequest(_request, _response, _accessControl.user); } - if (address(_request.resolutionModule) != address(0)) { + if (_request.resolutionModule != address(0)) { IResolutionModule(_request.resolutionModule).finalizeRequest(_request, _response, _accessControl.user); } diff --git a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol index 2f0c27e..ebc6fbd 100644 --- a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol +++ b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; +import {IAccessController} from '../../IAccessController.sol'; import {IModule} from '../../IModule.sol'; /** @@ -22,9 +23,8 @@ interface IAccessControlModule is IModule { */ struct AccessControlParameters { address sender; - address user; + IAccessController.AccessControl accessControl; bytes32 typehash; - bytes data; bytes params; } diff --git a/solidity/test/unit/AccessController.t.sol b/solidity/test/unit/AccessController.t.sol new file mode 100644 index 0000000..7f2549c --- /dev/null +++ b/solidity/test/unit/AccessController.t.sol @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {AccessController, IAccessControlModule, IAccessController} from '../../contracts/AccessController.sol'; +import {Helpers} from '../utils/Helpers.sol'; + +contract MockAccessControl is AccessController { + constructor() AccessController() {} + + function hasAccessForTest( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + AccessControl memory _accessControl + ) public hasAccess(_accessControlModule, _typehash, _params, _accessControl) {} +} + +/** + * @title Module Abstract Unit tests + */ +contract BaseTest is Test, Helpers { + // A mock access controller + MockAccessControl public accessController; + // Mock caller + address public caller; + + /** + * @notice Deploy the access controller + */ + function setUp() public { + accessController = new MockAccessControl(); + caller = makeAddr('caller'); + } +} + +contract AccessController_Unit_HasAccess is BaseTest { + modifier happyPath( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) { + vm.assume(_accessControl.user != caller); + vm.assume(_accessControlModule != address(0) && _accessControlModule != caller); + vm.assume(_params.length < 32); + vm.startPrank(caller); + + _; + } + + function test_revertIfNoHasAccess( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { + IAccessControlModule.AccessControlParameters memory _expectedParams = IAccessControlModule.AccessControlParameters({ + sender: caller, + accessControl: _accessControl, + typehash: _typehash, + params: _params + }); + + // Expect the hasAccess function to not be called + _mockAndExpect( + _accessControlModule, + abi.encodeWithSelector(IAccessControlModule.hasAccess.selector, abi.encode(_expectedParams)), + abi.encode(false) + ); + + // Expect the revert + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + + // Call the hasAccess function + accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); + } + + function test_revertIfAddressZero( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { + // Expect the revert + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + + // Call the hasAccess function + accessController.hasAccessForTest(address(0), _typehash, _params, _accessControl); + } + + function test_hasAccessSenderIsNotUser( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { + IAccessControlModule.AccessControlParameters memory _expectedParams = IAccessControlModule.AccessControlParameters({ + sender: caller, + accessControl: _accessControl, + typehash: _typehash, + params: _params + }); + + // Expect the hasAccess function to be called + _mockAndExpect( + _accessControlModule, + abi.encodeWithSelector(IAccessControlModule.hasAccess.selector, abi.encode(_expectedParams)), + abi.encode(true) + ); + + // Call the hasAccess function + accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); + } + + function test_hasAccessSenderIsUser( + address _accessControlModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { + _accessControl.user = caller; + // Call the hasAccess function + accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); + } +} From 3afab79135e52b2f0e4144930ae830dab1beb748 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Mon, 4 Nov 2024 11:51:00 -0300 Subject: [PATCH 09/19] feat: move logic and tests --- solidity/contracts/AccessController.sol | 28 +++++++++- solidity/contracts/Oracle.sol | 54 +++++++++---------- solidity/contracts/utils/OracleTypehash.sol | 3 ++ solidity/interfaces/IAccessController.sol | 51 +++++++++++++++++- solidity/interfaces/IOracle.sol | 38 +++---------- .../accessControl/IAccessControlModule.sol | 4 +- .../test/integration/EscalateDispute.t.sol | 4 +- solidity/test/integration/Finalization.t.sol | 4 +- .../test/integration/ResponseDispute.t.sol | 2 +- .../test/integration/ResponseProposal.t.sol | 8 +-- .../mocks/contracts/MockAtomicArbitrator.sol | 6 ++- solidity/test/unit/AccessController.t.sol | 39 +++++++++++++- solidity/test/unit/Oracle.t.sol | 44 +++++---------- 13 files changed, 176 insertions(+), 109 deletions(-) diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol index c91896d..20638a8 100644 --- a/solidity/contracts/AccessController.sol +++ b/solidity/contracts/AccessController.sol @@ -5,6 +5,21 @@ import {IAccessController} from '../interfaces/IAccessController.sol'; import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.sol'; abstract contract AccessController is IAccessController { + /// @inheritdoc IAccessController + mapping(address _user => mapping(address _accessControlModule => bool _approved)) public isAccessControlApproved; + + /** + * @notice Modifier to check if the user approved to the access control module + * @param _user The address of the user + * @param _accessControlModule The access control module to check if approved + */ + modifier isApproved(address _user, address _accessControlModule) { + if (_accessControlModule != address(0) && !isAccessControlApproved[_user][_accessControlModule]) { + revert AccessController_AccessControlModuleNotApproved(); + } + _; + } + /** * @notice Modifier to check if the caller has access to the user * @param _accessControlModule The access control module @@ -30,7 +45,18 @@ abstract contract AccessController is IAccessController { ) ) ); - if (!_hasAccess) revert AccessControlData_NoAccess(); + + if (!_hasAccess) revert AccessController_NoAccess(); _; } + + /// @inheritdoc IAccessController + function setAccessControlModule(address _accessControlModule, bool _approved) external { + if (isAccessControlApproved[msg.sender][_accessControlModule] == _approved) { + revert AccessController_AccessControlModuleAlreadySet(); + } + isAccessControlApproved[msg.sender][_accessControlModule] = _approved; + + emit AccessControlModuleSet(msg.sender, _accessControlModule, _approved); + } } diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index fca7dcb..cf160b3 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -46,9 +46,6 @@ contract Oracle is IOracle, AccessController, OracleTypehash { /// @inheritdoc IOracle mapping(bytes32 _requestId => mapping(address _user => bool _isParticipant)) public isParticipant; - /// @inheritdoc IOracle - mapping(address _user => mapping(address _accessControlModule => bool _approved)) public isAccessControlApproved; - /// @inheritdoc IOracle uint256 public totalRequestCount; @@ -57,18 +54,6 @@ contract Oracle is IOracle, AccessController, OracleTypehash { */ mapping(bytes32 _requestId => bytes _responseIds) internal _responseIds; - /** - * @notice Modifier to check if the user approved to the access control module - * @param _user The address of the user - * @param _accessControlModule The access control module to check if approved - */ - modifier isApproved(address _user, address _accessControlModule) { - if (_accessControlModule != address(0) && !isAccessControlApproved[_user][_accessControlModule]) { - revert Oracle_AccessControlModuleNotApproved(); - } - _; - } - /// @inheritdoc IOracle function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) { bytes memory _responses = _responseIds[_requestId]; @@ -110,13 +95,6 @@ contract Oracle is IOracle, AccessController, OracleTypehash { } } - /// @inheritdoc IOracle - function setAccessControlModule(address _accessControlModule, bool _approved) external { - isAccessControlApproved[msg.sender][_accessControlModule] = _approved; - - emit AccessControlModuleSet(msg.sender, _accessControlModule, _approved); - } - /// @inheritdoc IOracle function createRequest( Request calldata _request, @@ -162,7 +140,7 @@ contract Oracle is IOracle, AccessController, OracleTypehash { revert Oracle_InvalidRequest(); } - // The caller must be the proposer, unless the response is coming from a dispute module + // The user must be the proposer unless response comes from the dispute module if (_accessControl.user != _response.proposer && _accessControl.user != _request.disputeModule) { revert Oracle_InvalidProposer(); } @@ -269,7 +247,16 @@ contract Oracle is IOracle, AccessController, OracleTypehash { } /// @inheritdoc IOracle - function resolveDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external { + function resolveDispute( + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) + external + isApproved(_accessControl.user, _request.accessControlModule) + hasAccess(_request.accessControlModule, RESOLVE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); if (disputeCreatedAt[_disputeId] == 0) { @@ -286,20 +273,27 @@ contract Oracle is IOracle, AccessController, OracleTypehash { revert Oracle_CannotResolve(_disputeId); } - if (_request.resolutionModule == address(0)) { + // Needed to avoid stack too deep error + Request memory _usedRequest = _request; + Response memory _usedResponse = _response; + Dispute memory _usedDispute = _dispute; + + if (_usedRequest.resolutionModule == address(0)) { revert Oracle_NoResolutionModule(_disputeId); } - IResolutionModule(_request.resolutionModule).resolveDispute(_disputeId, _request, _response, _dispute); + IResolutionModule(_usedRequest.resolutionModule).resolveDispute( + _disputeId, _usedRequest, _usedResponse, _usedDispute + ); - emit DisputeResolved(_disputeId, _dispute); + emit DisputeResolved(_disputeId, _usedDispute); } /// @inheritdoc IOracle function updateDisputeStatus( - Request memory _request, - Response memory _response, - Dispute memory _dispute, + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, DisputeStatus _status ) external { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index b1e8bf7..ab04ef1 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -14,4 +14,7 @@ contract OracleTypehash { keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); bytes32 public constant FINALIZE_TYPEHASH = keccak256('Finalize(Request _request,Response _response)'); + + bytes32 public constant RESOLVE_TYPEHASH = + keccak256('ResolveDispute(Request _request,Response _response,Dispute _dispute)'); } diff --git a/solidity/interfaces/IAccessController.sol b/solidity/interfaces/IAccessController.sol index 9bd6854..5a69e68 100644 --- a/solidity/interfaces/IAccessController.sol +++ b/solidity/interfaces/IAccessController.sol @@ -6,6 +6,18 @@ pragma solidity ^0.8.19; * @notice Interface for the access controller */ interface IAccessController { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emitted when the access control module is set + * @param _user The address of the user + * @param _accessControlModule The address of the access control module + * @param _approved If the module is approved + */ + event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); + /*/////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -23,7 +35,42 @@ interface IAccessController { ERRORS //////////////////////////////////////////////////////////////*/ /** - * @notice Reverts if the caller has no access + * @notice Thrown when the caller has no access + */ + error AccessController_NoAccess(); + + /** + * @notice Thrown when user didn't approve the access control module + */ + error AccessController_AccessControlModuleNotApproved(); + + /** + * @notice Thrown when the access control module is already set + */ + error AccessController_AccessControlModuleAlreadySet(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Checks if the given address approved the access control module + * + * @param _user The address to check + * @param _accessControlModule The address of the access control module + * @return _approved If the user approved the access control module + */ + function isAccessControlApproved(address _user, address _accessControlModule) external view returns (bool _approved); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Sets the address of the access control module + * + * @param _accessControlModule The address of the access control module + * @param _approved If the module is approved */ - error AccessControlData_NoAccess(); + function setAccessControlModule(address _accessControlModule, bool _approved) external; } diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 3b8722b..f0d4e79 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -12,14 +12,6 @@ interface IOracle is IAccessController { EVENTS //////////////////////////////////////////////////////////////*/ - /** - * @notice Emitted when the access control module is set - * @param _user The address of the user - * @param _accessControlModule The address of the access control module - * @param _approved If the module is approved - */ - event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); - /** * @notice Emitted when a request is created * @param _requestId The id of the created request @@ -78,11 +70,6 @@ interface IOracle is IAccessController { ERRORS //////////////////////////////////////////////////////////////*/ - /** - * @notice Thrown when user didn't approve the access control module - */ - error Oracle_AccessControlModuleNotApproved(); - /** * @notice Thrown when an unauthorized caller is trying to change a dispute's status * @param _caller The caller of the function @@ -346,15 +333,6 @@ interface IOracle is IAccessController { */ function isParticipant(bytes32 _requestId, address _user) external view returns (bool _isParticipant); - /** - * @notice Checks if the given address approved the access control module - * - * @param _user The address to check - * @param _accessControlModule The address of the access control module - * @return _approved If the user approved the access control module - */ - function isAccessControlApproved(address _user, address _accessControlModule) external view returns (bool _approved); - /** * @notice Returns the total number of requests stored in the oracle * @@ -383,14 +361,6 @@ interface IOracle is IAccessController { LOGIC //////////////////////////////////////////////////////////////*/ - /** - * @notice Sets the address of the access control module - * - * @param _accessControlModule The address of the access control module - * @param _approved If the module is approved - */ - function setAccessControlModule(address _accessControlModule, bool _approved) external; - /** * @notice Generates the request ID and initializes the modules for the request * @@ -471,8 +441,14 @@ interface IOracle is IAccessController { * @param _request The request * @param _response The disputed response * @param _dispute The dispute that is being resolved + * @param _accessControl The access control data */ - function resolveDispute(Request calldata _request, Response calldata _response, Dispute calldata _dispute) external; + function resolveDispute( + Request calldata _request, + Response calldata _response, + Dispute calldata _dispute, + AccessControl calldata _accessControl + ) external; /** * @notice Updates the status of a dispute diff --git a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol index ebc6fbd..a29d49a 100644 --- a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol +++ b/solidity/interfaces/modules/accessControl/IAccessControlModule.sol @@ -5,8 +5,8 @@ import {IAccessController} from '../../IAccessController.sol'; import {IModule} from '../../IModule.sol'; /** - * @title ResponseModule - * @notice Common interface for all response modules + * @title AccessControlModule + * @notice Common interface for all access control modules */ interface IAccessControlModule is IModule { /*/////////////////////////////////////////////////////////////// diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index 62e3ecb..fa56a9b 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -19,12 +19,12 @@ contract Integration_EscalateDispute is IntegrationBase { mockAccessControl.user = _badDisputer; // Reverts because caller is not approved to dispute - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); // Dispute reverts if caller is not approved mockAccessControl.user = disputer; - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); vm.prank(badCaller); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); diff --git a/solidity/test/integration/Finalization.t.sol b/solidity/test/integration/Finalization.t.sol index 09a0156..42a89ee 100644 --- a/solidity/test/integration/Finalization.t.sol +++ b/solidity/test/integration/Finalization.t.sol @@ -67,7 +67,7 @@ contract Integration_Finalization is IntegrationBase { mockAccessControl.user = finalizer; vm.startPrank(badCaller); - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); oracle.finalize(mockRequest, mockResponse, mockAccessControl); vm.stopPrank(); @@ -130,7 +130,7 @@ contract Integration_Finalization is IntegrationBase { vm.stopPrank(); vm.startPrank(badCaller); - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); oracle.finalize(mockRequest, mockResponse, mockAccessControl); vm.stopPrank(); diff --git a/solidity/test/integration/ResponseDispute.t.sol b/solidity/test/integration/ResponseDispute.t.sol index c2cab00..348e349 100644 --- a/solidity/test/integration/ResponseDispute.t.sol +++ b/solidity/test/integration/ResponseDispute.t.sol @@ -46,7 +46,7 @@ contract Integration_ResponseDispute is IntegrationBase { mockAccessControl.user = _anotherDisputer; // Revert if the response is access control is not approved - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); // Revert if the response is already disputed diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index e9d1b43..dee2083 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -15,14 +15,14 @@ contract Integration_ResponseProposal is IntegrationBase { mockAccessControl.user = _badRequester; // Revert if not approved to create request - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); vm.stopPrank(); // Revert if has no access to create request mockAccessControl.user = requester; - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); vm.prank(badCaller); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); @@ -35,7 +35,7 @@ contract Integration_ResponseProposal is IntegrationBase { mockResponse.response = _response; // Revert if not approved to create request - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); address _badProposer = makeAddr('badProposer'); mockAccessControl.user = _badProposer; oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); @@ -44,7 +44,7 @@ contract Integration_ResponseProposal is IntegrationBase { // Revert if has no access to create request mockAccessControl.user = proposer; - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); vm.prank(badCaller); oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); diff --git a/solidity/test/mocks/contracts/MockAtomicArbitrator.sol b/solidity/test/mocks/contracts/MockAtomicArbitrator.sol index a807c10..2fb163d 100644 --- a/solidity/test/mocks/contracts/MockAtomicArbitrator.sol +++ b/solidity/test/mocks/contracts/MockAtomicArbitrator.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IOracle} from '../../../interfaces/IOracle.sol'; +import {IAccessController, IOracle} from '../../../interfaces/IOracle.sol'; contract MockAtomicArbitrator { IOracle.DisputeStatus public answer; @@ -18,7 +18,9 @@ contract MockAtomicArbitrator { ) external returns (bytes memory _result) { _result = new bytes(0); answer = IOracle.DisputeStatus.Won; - oracle.resolveDispute(_request, _response, _dispute); + oracle.resolveDispute( + _request, _response, _dispute, IAccessController.AccessControl({user: address(this), data: new bytes(0)}) + ); } function getAnswer(bytes32 /* _dispute */ ) external view returns (IOracle.DisputeStatus _answer) { diff --git a/solidity/test/unit/AccessController.t.sol b/solidity/test/unit/AccessController.t.sol index 7f2549c..5f42126 100644 --- a/solidity/test/unit/AccessController.t.sol +++ b/solidity/test/unit/AccessController.t.sol @@ -15,6 +15,10 @@ contract MockAccessControl is AccessController { bytes memory _params, AccessControl memory _accessControl ) public hasAccess(_accessControlModule, _typehash, _params, _accessControl) {} + + function setAccessControlModuleForTest(address _accessControlModule, bool _approved) public { + isAccessControlApproved[msg.sender][_accessControlModule] = _approved; + } } /** @@ -26,6 +30,9 @@ contract BaseTest is Test, Helpers { // Mock caller address public caller; + // Events + event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); + /** * @notice Deploy the access controller */ @@ -71,7 +78,7 @@ contract AccessController_Unit_HasAccess is BaseTest { ); // Expect the revert - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); // Call the hasAccess function accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); @@ -84,7 +91,7 @@ contract AccessController_Unit_HasAccess is BaseTest { IAccessController.AccessControl memory _accessControl ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { // Expect the revert - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessControlData_NoAccess.selector)); + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); // Call the hasAccess function accessController.hasAccessForTest(address(0), _typehash, _params, _accessControl); @@ -125,3 +132,31 @@ contract AccessController_Unit_HasAccess is BaseTest { accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); } } + +contract AccessControler_Unit_SetAccessControlModule is BaseTest { + function test_revertIfAccessControlModuleAlreadySet(address _accessControlModule, bool _approved) public { + vm.assume(_approved == true); + + // Set the access control module + accessController.setAccessControlModuleForTest(_accessControlModule, _approved); + + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleAlreadySet.selector)); + + // Test: set the access control module + accessController.setAccessControlModule(address(_accessControlModule), _approved); + } + + function test_setAccessControlModuleTrue(address _accessControlModule, bool _approved) public { + vm.assume(_approved == true); + + // Check: emits AccessControlModuleSet event? + _expectEmit(address(accessController)); + emit AccessControlModuleSet(address(this), address(_accessControlModule), _approved); + + // Test: set the access control module + accessController.setAccessControlModule(address(_accessControlModule), _approved); + + // Check: correct access control module set? + assertEq(accessController.isAccessControlApproved(address(this), address(_accessControlModule)), _approved); + } +} diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 0eddb24..ec8b33b 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -105,7 +105,6 @@ contract BaseTest is Test, Helpers { event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute); - event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); function setUp() public virtual { oracle = new MockOracle(); @@ -134,23 +133,6 @@ contract BaseTest is Test, Helpers { } } -contract Oracle_Unit_SetAccessControlModule is BaseTest { - /** - * @notice Test the access control module setter - */ - function test_setAccessControlModuleTrue(bool _approved) public { - // Check: emits AccessControlModuleSet event? - _expectEmit(address(oracle)); - emit AccessControlModuleSet(address(this), address(accessControlModule), _approved); - - // Test: set the access control module - oracle.setAccessControlModule(address(accessControlModule), _approved); - - // Check: correct access control module set? - assertEq(oracle.isAccessControlApproved(address(this), address(accessControlModule)), _approved); - } -} - contract Oracle_Unit_CreateRequest is BaseTest { modifier happyPath() { mockAccessControl.user = requester; @@ -223,7 +205,7 @@ contract Oracle_Unit_CreateRequest is BaseTest { */ function test_createRequest_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + vm.expectRevert(IAccessController.AccessController_AccessControlModuleNotApproved.selector); // Test: try to create the request oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); @@ -239,7 +221,7 @@ contract Oracle_Unit_CreateRequest is BaseTest { mockAccessControl.user = requester; // Check: revert? - vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + vm.expectRevert(IAccessController.AccessController_NoAccess.selector); // Test: try to create the request vm.prank(_caller); @@ -529,7 +511,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { */ function test_proposeResponse_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + vm.expectRevert(IAccessController.AccessController_AccessControlModuleNotApproved.selector); // Test: try to create the request oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); @@ -553,7 +535,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { mockAccessControl.user = proposer; // Check: revert? - vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + vm.expectRevert(IAccessController.AccessController_NoAccess.selector); // Test: try to propose a response from a random address vm.prank(_caller); @@ -667,7 +649,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { */ function test_disputeResponse_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + vm.expectRevert(IAccessController.AccessController_AccessControlModuleNotApproved.selector); // Test: try to create the request oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); @@ -711,7 +693,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { mockAccessControl.user = disputer; // Check: revert? - vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + vm.expectRevert(IAccessController.AccessController_NoAccess.selector); // Test: try to propose a response from a random address vm.prank(_caller); @@ -860,6 +842,8 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { contract Oracle_Unit_ResolveDispute is BaseTest { modifier happyPath() { + mockAccessControl.user = address(resolutionModule); + oracle.mock_setAccessControlApproved(address(resolutionModule), address(accessControlModule), true); vm.startPrank(address(resolutionModule)); _; } @@ -889,7 +873,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { emit DisputeResolved(_disputeId, mockDispute); // Test: resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } /** @@ -902,7 +886,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDisputeId.selector, _getId(mockDispute))); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } /** @@ -915,7 +899,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } /** @@ -942,7 +926,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotResolve.selector, _disputeId)); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } } @@ -972,7 +956,7 @@ contract Oracle_Unit_ResolveDispute is BaseTest { vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NoResolutionModule.selector, _disputeId)); // Test: try to resolve the dispute - oracle.resolveDispute(mockRequest, mockResponse, mockDispute); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } } @@ -1100,7 +1084,7 @@ contract Oracle_Unit_Finalize is BaseTest { mockAccessControl.user = address(requester); // Check: revert? - vm.expectRevert(IAccessController.AccessControlData_NoAccess.selector); + vm.expectRevert(IAccessController.AccessController_NoAccess.selector); // Test: try to finalize the request from a random address vm.prank(_caller); From 69e47b07963c8f1d49b6f2639f1407a67f5cf699 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Tue, 5 Nov 2024 00:34:26 -0300 Subject: [PATCH 10/19] fix: comments --- solidity/contracts/AccessController.sol | 25 ------------- solidity/contracts/Oracle.sol | 25 +++++++++++++ solidity/contracts/utils/OracleTypehash.sol | 16 +++++---- solidity/interfaces/IAccessController.sol | 35 ------------------- solidity/interfaces/IOracle.sol | 35 +++++++++++++++++++ .../test/integration/EscalateDispute.t.sol | 2 +- .../test/integration/ResponseDispute.t.sol | 2 +- .../test/integration/ResponseProposal.t.sol | 4 +-- solidity/test/unit/AccessController.t.sol | 35 ------------------- solidity/test/unit/Oracle.t.sol | 35 +++++++++++++++++-- 10 files changed, 105 insertions(+), 109 deletions(-) diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol index 20638a8..5cf0458 100644 --- a/solidity/contracts/AccessController.sol +++ b/solidity/contracts/AccessController.sol @@ -5,21 +5,6 @@ import {IAccessController} from '../interfaces/IAccessController.sol'; import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.sol'; abstract contract AccessController is IAccessController { - /// @inheritdoc IAccessController - mapping(address _user => mapping(address _accessControlModule => bool _approved)) public isAccessControlApproved; - - /** - * @notice Modifier to check if the user approved to the access control module - * @param _user The address of the user - * @param _accessControlModule The access control module to check if approved - */ - modifier isApproved(address _user, address _accessControlModule) { - if (_accessControlModule != address(0) && !isAccessControlApproved[_user][_accessControlModule]) { - revert AccessController_AccessControlModuleNotApproved(); - } - _; - } - /** * @notice Modifier to check if the caller has access to the user * @param _accessControlModule The access control module @@ -49,14 +34,4 @@ abstract contract AccessController is IAccessController { if (!_hasAccess) revert AccessController_NoAccess(); _; } - - /// @inheritdoc IAccessController - function setAccessControlModule(address _accessControlModule, bool _approved) external { - if (isAccessControlApproved[msg.sender][_accessControlModule] == _approved) { - revert AccessController_AccessControlModuleAlreadySet(); - } - isAccessControlApproved[msg.sender][_accessControlModule] = _approved; - - emit AccessControlModuleSet(msg.sender, _accessControlModule, _approved); - } } diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index cf160b3..fa978e7 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -46,6 +46,9 @@ contract Oracle is IOracle, AccessController, OracleTypehash { /// @inheritdoc IOracle mapping(bytes32 _requestId => mapping(address _user => bool _isParticipant)) public isParticipant; + /// @inheritdoc IOracle + mapping(address _user => mapping(address _accessControlModule => bool _approved)) public isAccessControlApproved; + /// @inheritdoc IOracle uint256 public totalRequestCount; @@ -54,6 +57,18 @@ contract Oracle is IOracle, AccessController, OracleTypehash { */ mapping(bytes32 _requestId => bytes _responseIds) internal _responseIds; + /** + * @notice Modifier to check if the user approved to the access control module + * @param _user The address of the user + * @param _accessControlModule The access control module to check if approved + */ + modifier isApproved(address _user, address _accessControlModule) { + if (_accessControlModule != address(0) && !isAccessControlApproved[_user][_accessControlModule]) { + revert Oracle_AccessControlModuleNotApproved(); + } + _; + } + /// @inheritdoc IOracle function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) { bytes memory _responses = _responseIds[_requestId]; @@ -95,6 +110,16 @@ contract Oracle is IOracle, AccessController, OracleTypehash { } } + /// @inheritdoc IOracle + function setAccessControlModule(address _accessControlModule, bool _approved) external { + if (isAccessControlApproved[msg.sender][_accessControlModule] == _approved) { + revert Oracle_AccessControlModuleAlreadySet(); + } + isAccessControlApproved[msg.sender][_accessControlModule] = _approved; + + emit AccessControlModuleSet(msg.sender, _accessControlModule, _approved); + } + /// @inheritdoc IOracle function createRequest( Request calldata _request, diff --git a/solidity/contracts/utils/OracleTypehash.sol b/solidity/contracts/utils/OracleTypehash.sol index ab04ef1..4ffb3e0 100644 --- a/solidity/contracts/utils/OracleTypehash.sol +++ b/solidity/contracts/utils/OracleTypehash.sol @@ -3,18 +3,20 @@ pragma solidity ^0.8.19; contract OracleTypehash { bytes32 public constant CREATE_TYPEHASH = - keccak256('CreateRequest(Request _request,bytes32 _ipfsHash,AccessControl _accessControl'); + keccak256('createRequest(Request _request,bytes32 _ipfsHash,AccessControl _accessControl'); - bytes32 public constant PROPOSE_TYPEHASH = keccak256('ProposeResponse(Request _request,Response _response)'); + bytes32 public constant PROPOSE_TYPEHASH = + keccak256('proposeResponse(Request _request,Response _response,AccessControl _accessControl)'); bytes32 public constant DISPUTE_TYPEHASH = - keccak256('DisputeResponse(Request _request,Response _response,Dispute _dispute)'); + keccak256('disputeResponse(Request _request,Response _response,Dispute _dispute,AccessControl _accessControl)'); bytes32 public constant ESCALATE_TYPEHASH = - keccak256('EscalateDispute(Request _request,Response _response,Dispute _dispute)'); - - bytes32 public constant FINALIZE_TYPEHASH = keccak256('Finalize(Request _request,Response _response)'); + keccak256('escalateDispute(Request _request,Response _response,Dispute _dispute,AccessControl _accessControl)'); bytes32 public constant RESOLVE_TYPEHASH = - keccak256('ResolveDispute(Request _request,Response _response,Dispute _dispute)'); + keccak256('resolveDispute(Request _request,Response _response,Dispute _dispute,AccessControl _accessControl)'); + + bytes32 public constant FINALIZE_TYPEHASH = + keccak256('finalize(Request _request,Response _response,AccessControl _accessControl)'); } diff --git a/solidity/interfaces/IAccessController.sol b/solidity/interfaces/IAccessController.sol index 5a69e68..944a5c9 100644 --- a/solidity/interfaces/IAccessController.sol +++ b/solidity/interfaces/IAccessController.sol @@ -10,14 +10,6 @@ interface IAccessController { EVENTS //////////////////////////////////////////////////////////////*/ - /** - * @notice Emitted when the access control module is set - * @param _user The address of the user - * @param _accessControlModule The address of the access control module - * @param _approved If the module is approved - */ - event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); - /*/////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -39,38 +31,11 @@ interface IAccessController { */ error AccessController_NoAccess(); - /** - * @notice Thrown when user didn't approve the access control module - */ - error AccessController_AccessControlModuleNotApproved(); - - /** - * @notice Thrown when the access control module is already set - */ - error AccessController_AccessControlModuleAlreadySet(); - /*/////////////////////////////////////////////////////////////// VARIABLES //////////////////////////////////////////////////////////////*/ - /** - * @notice Checks if the given address approved the access control module - * - * @param _user The address to check - * @param _accessControlModule The address of the access control module - * @return _approved If the user approved the access control module - */ - function isAccessControlApproved(address _user, address _accessControlModule) external view returns (bool _approved); - /*/////////////////////////////////////////////////////////////// LOGIC //////////////////////////////////////////////////////////////*/ - - /** - * @notice Sets the address of the access control module - * - * @param _accessControlModule The address of the access control module - * @param _approved If the module is approved - */ - function setAccessControlModule(address _accessControlModule, bool _approved) external; } diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index f0d4e79..b6d6b6f 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -66,6 +66,14 @@ interface IOracle is IAccessController { */ event DisputeResolved(bytes32 indexed _disputeId, Dispute _dispute); + /** + * @notice Emitted when the access control module is set + * @param _user The address of the user + * @param _accessControlModule The address of the access control module + * @param _approved If the module is approved + */ + event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); + /*/////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -168,6 +176,16 @@ interface IOracle is IAccessController { */ error Oracle_InvalidDisputer(); + /** + * @notice Thrown when user didn't approve the access control module + */ + error Oracle_AccessControlModuleNotApproved(); + + /** + * @notice Thrown when the access control module is already set + */ + error Oracle_AccessControlModuleAlreadySet(); + /*/////////////////////////////////////////////////////////////// ENUMS //////////////////////////////////////////////////////////////*/ @@ -357,6 +375,15 @@ interface IOracle is IAccessController { */ function getResponseIds(bytes32 _requestId) external view returns (bytes32[] memory _ids); + /** + * @notice Checks if the given address approved the access control module + * + * @param _user The address to check + * @param _accessControlModule The address of the access control module + * @return _approved If the user approved the access control module + */ + function isAccessControlApproved(address _user, address _accessControlModule) external view returns (bool _approved); + /*/////////////////////////////////////////////////////////////// LOGIC //////////////////////////////////////////////////////////////*/ @@ -478,4 +505,12 @@ interface IOracle is IAccessController { Response calldata _response, AccessControl calldata _accessControl ) external; + + /** + * @notice Sets the address of the access control module + * + * @param _accessControlModule The address of the access control module + * @param _approved If the module is approved + */ + function setAccessControlModule(address _accessControlModule, bool _approved) external; } diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index fa56a9b..ea80e01 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -19,7 +19,7 @@ contract Integration_EscalateDispute is IntegrationBase { mockAccessControl.user = _badDisputer; // Reverts because caller is not approved to dispute - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); // Dispute reverts if caller is not approved diff --git a/solidity/test/integration/ResponseDispute.t.sol b/solidity/test/integration/ResponseDispute.t.sol index 348e349..c2cab00 100644 --- a/solidity/test/integration/ResponseDispute.t.sol +++ b/solidity/test/integration/ResponseDispute.t.sol @@ -46,7 +46,7 @@ contract Integration_ResponseDispute is IntegrationBase { mockAccessControl.user = _anotherDisputer; // Revert if the response is access control is not approved - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); // Revert if the response is already disputed diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index dee2083..e6311d0 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -15,7 +15,7 @@ contract Integration_ResponseProposal is IntegrationBase { mockAccessControl.user = _badRequester; // Revert if not approved to create request - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); vm.stopPrank(); @@ -35,7 +35,7 @@ contract Integration_ResponseProposal is IntegrationBase { mockResponse.response = _response; // Revert if not approved to create request - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleNotApproved.selector)); + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); address _badProposer = makeAddr('badProposer'); mockAccessControl.user = _badProposer; oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); diff --git a/solidity/test/unit/AccessController.t.sol b/solidity/test/unit/AccessController.t.sol index 5f42126..8dcb01a 100644 --- a/solidity/test/unit/AccessController.t.sol +++ b/solidity/test/unit/AccessController.t.sol @@ -15,10 +15,6 @@ contract MockAccessControl is AccessController { bytes memory _params, AccessControl memory _accessControl ) public hasAccess(_accessControlModule, _typehash, _params, _accessControl) {} - - function setAccessControlModuleForTest(address _accessControlModule, bool _approved) public { - isAccessControlApproved[msg.sender][_accessControlModule] = _approved; - } } /** @@ -30,9 +26,6 @@ contract BaseTest is Test, Helpers { // Mock caller address public caller; - // Events - event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); - /** * @notice Deploy the access controller */ @@ -132,31 +125,3 @@ contract AccessController_Unit_HasAccess is BaseTest { accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); } } - -contract AccessControler_Unit_SetAccessControlModule is BaseTest { - function test_revertIfAccessControlModuleAlreadySet(address _accessControlModule, bool _approved) public { - vm.assume(_approved == true); - - // Set the access control module - accessController.setAccessControlModuleForTest(_accessControlModule, _approved); - - vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_AccessControlModuleAlreadySet.selector)); - - // Test: set the access control module - accessController.setAccessControlModule(address(_accessControlModule), _approved); - } - - function test_setAccessControlModuleTrue(address _accessControlModule, bool _approved) public { - vm.assume(_approved == true); - - // Check: emits AccessControlModuleSet event? - _expectEmit(address(accessController)); - emit AccessControlModuleSet(address(this), address(_accessControlModule), _approved); - - // Test: set the access control module - accessController.setAccessControlModule(address(_accessControlModule), _approved); - - // Check: correct access control module set? - assertEq(accessController.isAccessControlApproved(address(this), address(_accessControlModule)), _approved); - } -} diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index ec8b33b..216f3a2 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -105,6 +105,7 @@ contract BaseTest is Test, Helpers { event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute); + event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); function setUp() public virtual { oracle = new MockOracle(); @@ -205,7 +206,7 @@ contract Oracle_Unit_CreateRequest is BaseTest { */ function test_createRequest_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IAccessController.AccessController_AccessControlModuleNotApproved.selector); + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); // Test: try to create the request oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); @@ -511,7 +512,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { */ function test_proposeResponse_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IAccessController.AccessController_AccessControlModuleNotApproved.selector); + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); // Test: try to create the request oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); @@ -649,7 +650,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { */ function test_disputeResponse_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IAccessController.AccessController_AccessControlModuleNotApproved.selector); + vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); // Test: try to create the request oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); @@ -1424,3 +1425,31 @@ contract Oracle_Unit_EscalateDispute is BaseTest { oracle.escalateDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } } + +contract Oracle_Unit_SetAccessControlModule is BaseTest { + function test_revertIfAccessControlModuleAlreadySet(address _accessControlModule, bool _approved) public { + vm.assume(_approved == true); + + // Set the access control module + oracle.mock_setAccessControlApproved(address(this), _accessControlModule, _approved); + + vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleAlreadySet.selector)); + + // Test: set the access control module + oracle.setAccessControlModule(address(_accessControlModule), _approved); + } + + function test_setAccessControlModuleTrue(address _accessControlModule, bool _approved) public { + vm.assume(_approved == true); + + // Check: emits AccessControlModuleSet event? + _expectEmit(address(oracle)); + emit AccessControlModuleSet(address(this), address(_accessControlModule), _approved); + + // Test: set the access control module + oracle.setAccessControlModule(address(_accessControlModule), _approved); + + // Check: correct access control module set? + assertEq(oracle.isAccessControlApproved(address(this), address(_accessControlModule)), _approved); + } +} From 2fc32d4853ef35c09631addf62e40deb7fe07ed1 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Wed, 6 Nov 2024 15:17:19 -0300 Subject: [PATCH 11/19] feat: eoa test --- .../test/integration/ResponseProposal.t.sol | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index e6311d0..dd5a6a3 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -20,14 +20,27 @@ contract Integration_ResponseProposal is IntegrationBase { vm.stopPrank(); - // Revert if has no access to create request + // Set access control module as an EOA + address _eoaAccessControlModule = makeAddr('eoaAccessControlModule'); + vm.prank(requester); + oracle.setAccessControlModule(_eoaAccessControlModule, true); + mockAccessControl.user = requester; + + // Revert if has no access to create request vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); vm.prank(badCaller); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); - // Create request vm.startPrank(caller); + + // Revert if the access control module is an EOA + mockRequest.accessControlModule = _eoaAccessControlModule; + vm.expectRevert(); + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + // Create request + mockRequest.accessControlModule = address(_accessControlModule); _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); } From 69efe2b4d4c96899112c2c818cde990c36ebc70d Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Wed, 6 Nov 2024 15:55:39 -0300 Subject: [PATCH 12/19] fix: access module finalize --- solidity/contracts/Oracle.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index fa978e7..af35ebc 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -5,6 +5,7 @@ import {IOracle} from '../interfaces/IOracle.sol'; import {IDisputeModule} from '../interfaces/modules/dispute/IDisputeModule.sol'; +import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.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'; @@ -377,6 +378,7 @@ contract Oracle is IOracle, AccessController, OracleTypehash { IDisputeModule(_request.disputeModule).finalizeRequest(_request, _response, _accessControl.user); IResponseModule(_request.responseModule).finalizeRequest(_request, _response, _accessControl.user); IRequestModule(_request.requestModule).finalizeRequest(_request, _response, _accessControl.user); + IAccessControlModule(_request.accessControlModule).finalizeRequest(_request, _response, _accessControl.user); emit OracleRequestFinalized(_requestId, _responseId); } From 2b68ec06070c629f71595dc0a146ecd2af6039ac Mon Sep 17 00:00:00 2001 From: xorsal Date: Thu, 7 Nov 2024 13:31:44 -0500 Subject: [PATCH 13/19] refactor: access module (#53) --- solidity/contracts/AccessController.sol | 37 ------ solidity/contracts/CommonAccessController.sol | 39 +++++++ solidity/contracts/Oracle.sol | 58 +++------ solidity/contracts/OracleAccessController.sol | 42 +++++++ solidity/interfaces/IAccessController.sol | 17 +-- solidity/interfaces/IOracle.sol | 43 +------ .../interfaces/IOracleAccessController.sol | 65 +++++++++++ .../IAccessModule.sol} | 10 +- .../test/integration/EscalateDispute.t.sol | 4 +- solidity/test/integration/IntegrationBase.sol | 18 +-- .../test/integration/ResponseDispute.t.sol | 4 +- .../test/integration/ResponseProposal.t.sol | 21 +++- ...ControlModule.sol => MockAccessModule.sol} | 12 +- ...ontrolModule.sol => IMockAccessModule.sol} | 4 +- solidity/test/unit/AccessController.t.sol | 53 ++++----- solidity/test/unit/Oracle.t.sol | 110 +++++++++--------- solidity/test/utils/Helpers.sol | 2 +- 17 files changed, 309 insertions(+), 230 deletions(-) delete mode 100644 solidity/contracts/AccessController.sol create mode 100644 solidity/contracts/CommonAccessController.sol create mode 100644 solidity/contracts/OracleAccessController.sol create mode 100644 solidity/interfaces/IOracleAccessController.sol rename solidity/interfaces/modules/{accessControl/IAccessControlModule.sol => access/IAccessModule.sol} (84%) rename solidity/test/mocks/contracts/{MockAccessControlModule.sol => MockAccessModule.sol} (55%) rename solidity/test/mocks/interfaces/{IMockAccessControlModule.sol => IMockAccessModule.sol} (55%) diff --git a/solidity/contracts/AccessController.sol b/solidity/contracts/AccessController.sol deleted file mode 100644 index 5cf0458..0000000 --- a/solidity/contracts/AccessController.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {IAccessController} from '../interfaces/IAccessController.sol'; -import {IAccessControlModule} from '../interfaces/modules/accessControl/IAccessControlModule.sol'; - -abstract contract AccessController is IAccessController { - /** - * @notice Modifier to check if the caller has access to the user - * @param _accessControlModule The access control module - * @param _accessControl The access control struct - */ - modifier hasAccess( - address _accessControlModule, - bytes32 _typehash, - bytes memory _params, - AccessControl memory _accessControl - ) { - bool _hasAccess = msg.sender == _accessControl.user - || ( - _accessControlModule != address(0) - && IAccessControlModule(_accessControlModule).hasAccess( - abi.encode( - IAccessControlModule.AccessControlParameters({ - sender: msg.sender, - accessControl: _accessControl, - typehash: _typehash, - params: _params - }) - ) - ) - ); - - if (!_hasAccess) revert AccessController_NoAccess(); - _; - } -} diff --git a/solidity/contracts/CommonAccessController.sol b/solidity/contracts/CommonAccessController.sol new file mode 100644 index 0000000..b0e6499 --- /dev/null +++ b/solidity/contracts/CommonAccessController.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IAccessController} from '../interfaces/IAccessController.sol'; +import {IAccessModule} from '../interfaces/modules/access/IAccessModule.sol'; + +abstract contract CommonAccessController is IAccessController { + /** + * @notice Check whether the caller is authorized for the given parameters. + * @param _accessModule The access module + * @param _typehash The typehash + * @param _typehashParams The params passed to the typehash + * @param _accessControl The access control struct + */ + function _hasAccess( + address _accessModule, + bytes32 _typehash, + bytes memory _typehashParams, + AccessControl memory _accessControl + ) internal { + // todo: if _accessModule == address(0) we should skip this check. + bool _granted = msg.sender == _accessControl.user + || ( + _accessModule != address(0) + && IAccessModule(_accessModule).hasAccess( + abi.encode( + IAccessModule.AccessControlParameters({ + sender: msg.sender, + accessControl: _accessControl, + typehash: _typehash, + typehashParams: _typehashParams + }) + ) + ) + ); + + if (!_granted) revert AccessController_NoAccess(); + } +} diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index fa978e7..f4719f2 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -5,15 +5,16 @@ import {IOracle} from '../interfaces/IOracle.sol'; import {IDisputeModule} from '../interfaces/modules/dispute/IDisputeModule.sol'; +import {IAccessModule} from '../interfaces/modules/access/IAccessModule.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 {ValidatorLib} from '../libraries/ValidatorLib.sol'; -import {AccessController} from './AccessController.sol'; +import {OracleAccessController} from './OracleAccessController.sol'; import {OracleTypehash} from './utils/OracleTypehash.sol'; -contract Oracle is IOracle, AccessController, OracleTypehash { +contract Oracle is IOracle, OracleAccessController, OracleTypehash { using ValidatorLib for *; /// @inheritdoc IOracle @@ -46,9 +47,6 @@ contract Oracle is IOracle, AccessController, OracleTypehash { /// @inheritdoc IOracle mapping(bytes32 _requestId => mapping(address _user => bool _isParticipant)) public isParticipant; - /// @inheritdoc IOracle - mapping(address _user => mapping(address _accessControlModule => bool _approved)) public isAccessControlApproved; - /// @inheritdoc IOracle uint256 public totalRequestCount; @@ -57,18 +55,6 @@ contract Oracle is IOracle, AccessController, OracleTypehash { */ mapping(bytes32 _requestId => bytes _responseIds) internal _responseIds; - /** - * @notice Modifier to check if the user approved to the access control module - * @param _user The address of the user - * @param _accessControlModule The access control module to check if approved - */ - modifier isApproved(address _user, address _accessControlModule) { - if (_accessControlModule != address(0) && !isAccessControlApproved[_user][_accessControlModule]) { - revert Oracle_AccessControlModuleNotApproved(); - } - _; - } - /// @inheritdoc IOracle function getResponseIds(bytes32 _requestId) public view returns (bytes32[] memory _ids) { bytes memory _responses = _responseIds[_requestId]; @@ -110,16 +96,6 @@ contract Oracle is IOracle, AccessController, OracleTypehash { } } - /// @inheritdoc IOracle - function setAccessControlModule(address _accessControlModule, bool _approved) external { - if (isAccessControlApproved[msg.sender][_accessControlModule] == _approved) { - revert Oracle_AccessControlModuleAlreadySet(); - } - isAccessControlApproved[msg.sender][_accessControlModule] = _approved; - - emit AccessControlModuleSet(msg.sender, _accessControlModule, _approved); - } - /// @inheritdoc IOracle function createRequest( Request calldata _request, @@ -153,8 +129,8 @@ contract Oracle is IOracle, AccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) + isApproved(_accessControl.user, _request.accessModule) + hasAccess(_request.accessModule, PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) returns (bytes32 _responseId) { _responseId = ValidatorLib._validateResponse(_request, _response); @@ -194,8 +170,8 @@ contract Oracle is IOracle, AccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + isApproved(_accessControl.user, _request.accessModule) + hasAccess(_request.accessModule, DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) returns (bytes32 _disputeId) { bytes32 _responseId; @@ -240,8 +216,8 @@ contract Oracle is IOracle, AccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + isApproved(_accessControl.user, _request.accessModule) + hasAccess(_request.accessModule, ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -279,8 +255,8 @@ contract Oracle is IOracle, AccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, RESOLVE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) + isApproved(_accessControl.user, _request.accessModule) + hasAccess(_request.accessModule, RESOLVE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -347,8 +323,8 @@ contract Oracle is IOracle, AccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) + isApproved(_accessControl.user, _request.accessModule) + hasAccess(_request.accessModule, FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) { bytes32 _requestId; bytes32 _responseId; @@ -378,6 +354,10 @@ contract Oracle is IOracle, AccessController, OracleTypehash { IResponseModule(_request.responseModule).finalizeRequest(_request, _response, _accessControl.user); IRequestModule(_request.requestModule).finalizeRequest(_request, _response, _accessControl.user); + if (_request.accessModule != address(0)) { + IAccessModule(_request.accessModule).finalizeRequest(_request, _response, _accessControl.user); + } + emit OracleRequestFinalized(_requestId, _responseId); } @@ -458,8 +438,8 @@ contract Oracle is IOracle, AccessController, OracleTypehash { AccessControl calldata _accessControl ) internal - isApproved(_accessControl.user, _request.accessControlModule) - hasAccess(_request.accessControlModule, CREATE_TYPEHASH, abi.encode(_request), _accessControl) + isApproved(_accessControl.user, _request.accessModule) + hasAccess(_request.accessModule, CREATE_TYPEHASH, abi.encode(_request), _accessControl) returns (bytes32 _requestId) { uint256 _requestNonce = totalRequestCount++; diff --git a/solidity/contracts/OracleAccessController.sol b/solidity/contracts/OracleAccessController.sol new file mode 100644 index 0000000..42ebfea --- /dev/null +++ b/solidity/contracts/OracleAccessController.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IOracleAccessController} from '../interfaces/IOracleAccessController.sol'; +import {CommonAccessController} from './CommonAccessController.sol'; + +abstract contract OracleAccessController is IOracleAccessController, CommonAccessController { + /// @inheritdoc IOracleAccessController + mapping(address _user => mapping(address _accessModule => bool _approved)) public isAccessModuleApproved; + + modifier hasAccess( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + AccessControl memory _accessControl + ) { + _hasAccess(_accessModule, _typehash, _params, _accessControl); + _; + } + + /** + * @notice Modifier to check if the user approved to the access module + * @param _user The address of setAccessModulethe user + * @param _accessModule The access module to check if approved + */ + modifier isApproved(address _user, address _accessModule) { + if (_accessModule != address(0) && !isAccessModuleApproved[_user][_accessModule]) { + revert OracleAccessController_AccessModuleNotApproved(); + } + _; + } + + /// @inheritdoc IOracleAccessController + function setAccessModule(address _accessModule, bool _approved) external { + if (isAccessModuleApproved[msg.sender][_accessModule] == _approved) { + revert OracleAccessController_AccessModuleAlreadySet(); + } + isAccessModuleApproved[msg.sender][_accessModule] = _approved; + + emit AccessModuleSet(msg.sender, _accessModule, _approved); + } +} diff --git a/solidity/interfaces/IAccessController.sol b/solidity/interfaces/IAccessController.sol index 944a5c9..dc29b44 100644 --- a/solidity/interfaces/IAccessController.sol +++ b/solidity/interfaces/IAccessController.sol @@ -10,9 +10,18 @@ interface IAccessController { EVENTS //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Thrown when the caller has no access + */ + error AccessController_NoAccess(); + /*/////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ + /** * @notice The access control struct * @param user The address of the user @@ -23,14 +32,6 @@ interface IAccessController { bytes data; } - /*/////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Thrown when the caller has no access - */ - error AccessController_NoAccess(); - /*/////////////////////////////////////////////////////////////// VARIABLES //////////////////////////////////////////////////////////////*/ diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index b6d6b6f..a0085af 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -1,13 +1,13 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IAccessController} from './IAccessController.sol'; +import {IAccessController, IOracleAccessController} from './IOracleAccessController.sol'; /** * @title Oracle * @notice The main contract storing requests, responses and disputes, and routing the calls to the modules. */ -interface IOracle is IAccessController { +interface IOracle is IOracleAccessController { /*/////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ @@ -66,14 +66,6 @@ interface IOracle is IAccessController { */ event DisputeResolved(bytes32 indexed _disputeId, Dispute _dispute); - /** - * @notice Emitted when the access control module is set - * @param _user The address of the user - * @param _accessControlModule The address of the access control module - * @param _approved If the module is approved - */ - event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); - /*/////////////////////////////////////////////////////////////// ERRORS //////////////////////////////////////////////////////////////*/ @@ -176,16 +168,6 @@ interface IOracle is IAccessController { */ error Oracle_InvalidDisputer(); - /** - * @notice Thrown when user didn't approve the access control module - */ - error Oracle_AccessControlModuleNotApproved(); - - /** - * @notice Thrown when the access control module is already set - */ - error Oracle_AccessControlModuleAlreadySet(); - /*/////////////////////////////////////////////////////////////// ENUMS //////////////////////////////////////////////////////////////*/ @@ -214,7 +196,7 @@ interface IOracle is IAccessController { * @param disputeModule The address of the dispute module * @param resolutionModule The address of the resolution module * @param finalityModule The address of the finality module - * @param accessControlModule The address of the access control module + * @param accessModule The address of the access module * @param requestModuleData The parameters for the request module * @param responseModuleData The parameters for the response module * @param disputeModuleData The parameters for the dispute module @@ -231,7 +213,7 @@ interface IOracle is IAccessController { address disputeModule; address resolutionModule; address finalityModule; - address accessControlModule; + address accessModule; bytes requestModuleData; bytes responseModuleData; bytes disputeModuleData; @@ -375,15 +357,6 @@ interface IOracle is IAccessController { */ function getResponseIds(bytes32 _requestId) external view returns (bytes32[] memory _ids); - /** - * @notice Checks if the given address approved the access control module - * - * @param _user The address to check - * @param _accessControlModule The address of the access control module - * @return _approved If the user approved the access control module - */ - function isAccessControlApproved(address _user, address _accessControlModule) external view returns (bool _approved); - /*/////////////////////////////////////////////////////////////// LOGIC //////////////////////////////////////////////////////////////*/ @@ -505,12 +478,4 @@ interface IOracle is IAccessController { Response calldata _response, AccessControl calldata _accessControl ) external; - - /** - * @notice Sets the address of the access control module - * - * @param _accessControlModule The address of the access control module - * @param _approved If the module is approved - */ - function setAccessControlModule(address _accessControlModule, bool _approved) external; } diff --git a/solidity/interfaces/IOracleAccessController.sol b/solidity/interfaces/IOracleAccessController.sol new file mode 100644 index 0000000..50859a8 --- /dev/null +++ b/solidity/interfaces/IOracleAccessController.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import {IAccessController} from './IAccessController.sol'; + +/** + * @title Oracle Access Controller Interface + * @notice Interface for the oracle access controller + */ +interface IOracleAccessController is IAccessController { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Emitted when a access module is set or updated + * @param _user The address of the user + * @param _accessModule The address of the access module + * @param _approved True if approved, false otherwise + */ + event AccessModuleSet(address indexed _user, address indexed _accessModule, bool _approved); + + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Thrown when user has not approved the access module + */ + error OracleAccessController_AccessModuleNotApproved(); + + /** + * @notice Thrown when the access module is already set + */ + error OracleAccessController_AccessModuleAlreadySet(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Checks if the given address has approved the access module + * + * @param _user The address to check + * @param _accessModule The address of the access module + * @return _approved True if the user approved the access module, false otherwise + */ + function isAccessModuleApproved(address _user, address _accessModule) external view returns (bool _approved); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + + /** + * @notice Sets the approval of an the access module address. + * + * @param _accessModule The address of the access module + * @param _approved True to approve, false otherwise. + */ + function setAccessModule(address _accessModule, bool _approved) external; +} diff --git a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol b/solidity/interfaces/modules/access/IAccessModule.sol similarity index 84% rename from solidity/interfaces/modules/accessControl/IAccessControlModule.sol rename to solidity/interfaces/modules/access/IAccessModule.sol index a29d49a..05c2592 100644 --- a/solidity/interfaces/modules/accessControl/IAccessControlModule.sol +++ b/solidity/interfaces/modules/access/IAccessModule.sol @@ -5,10 +5,10 @@ import {IAccessController} from '../../IAccessController.sol'; import {IModule} from '../../IModule.sol'; /** - * @title AccessControlModule - * @notice Common interface for all access control modules + * @title AccessModule + * @notice Common interface for all access modules */ -interface IAccessControlModule is IModule { +interface IAccessModule is IModule { /*/////////////////////////////////////////////////////////////// STRUCTS //////////////////////////////////////////////////////////////*/ @@ -25,14 +25,14 @@ interface IAccessControlModule is IModule { address sender; IAccessController.AccessControl accessControl; bytes32 typehash; - bytes params; + bytes typehashParams; } /*/////////////////////////////////////////////////////////////// LOGIC //////////////////////////////////////////////////////////////*/ /** - * @notice Checks if the caller has access to the user + * @notice Check whether the caller is authorized for the given parameters. * @param _data The data for access control validation * @return _hasAccess True if the caller has access to the user */ diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index ea80e01..8c3b140 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -19,7 +19,9 @@ contract Integration_EscalateDispute is IntegrationBase { mockAccessControl.user = _badDisputer; // Reverts because caller is not approved to dispute - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert( + abi.encodeWithSelector(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector) + ); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); // Dispute reverts if caller is not approved diff --git a/solidity/test/integration/IntegrationBase.sol b/solidity/test/integration/IntegrationBase.sol index 4aedb45..425278f 100644 --- a/solidity/test/integration/IntegrationBase.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -7,21 +7,21 @@ import {console} from 'forge-std/console.sol'; import {IDisputeModule} from '../../interfaces/modules/dispute/IDisputeModule.sol'; -import {IAccessControlModule} from '../../interfaces/modules/accessControl/IAccessControlModule.sol'; +import {IAccessModule} from '../../interfaces/modules/access/IAccessModule.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 {IOracle, Oracle} from '../../contracts/Oracle.sol'; -import {IAccessController} from '../../interfaces/IAccessController.sol'; +import {IAccessController, IOracleAccessController} from '../../interfaces/IOracleAccessController.sol'; import {IMockAccounting, MockAccounting} from '../mocks/contracts/MockAccounting.sol'; import {MockCallback} from '../mocks/contracts/MockCallback.sol'; import {IMockDisputeModule, MockDisputeModule} from '../mocks/contracts/MockDisputeModule.sol'; -import {IMockAccessControlModule, MockAccessControlModule} from '../mocks/contracts/MockAccessControlModule.sol'; +import {IMockAccessModule, MockAccessModule} from '../mocks/contracts/MockAccessModule.sol'; import {IMockFinalityModule, MockFinalityModule} from '../mocks/contracts/MockFinalityModule.sol'; import {IMockRequestModule, MockRequestModule} from '../mocks/contracts/MockRequestModule.sol'; import {IMockResolutionModule, MockResolutionModule} from '../mocks/contracts/MockResolutionModule.sol'; @@ -51,7 +51,7 @@ contract IntegrationBase is TestConstants, Helpers { MockDisputeModule internal _disputeModule; MockResolutionModule internal _resolutionModule; MockFinalityModule internal _finalityModule; - MockAccessControlModule internal _accessControlModule; + MockAccessModule internal _accessModule; MockCallback internal _mockCallback; IERC20 public usdc = IERC20(_label(USDC_ADDRESS, 'USDC')); @@ -92,7 +92,7 @@ contract IntegrationBase is TestConstants, Helpers { _disputeModule = new MockDisputeModule(oracle); _resolutionModule = new MockResolutionModule(oracle); _finalityModule = new MockFinalityModule(oracle); - _accessControlModule = new MockAccessControlModule(oracle); + _accessModule = new MockAccessModule(oracle); vm.stopPrank(); @@ -138,7 +138,7 @@ contract IntegrationBase is TestConstants, Helpers { mockRequest.disputeModule = address(_disputeModule); mockRequest.resolutionModule = address(_resolutionModule); mockRequest.finalityModule = address(_finalityModule); - mockRequest.accessControlModule = address(_accessControlModule); + mockRequest.accessModule = address(_accessModule); mockRequest.requester = requester; // Configure the mock response @@ -148,7 +148,7 @@ contract IntegrationBase is TestConstants, Helpers { mockDispute.requestId = _getId(mockRequest); mockDispute.responseId = _getId(mockResponse); - // Add the allowed callers to the access control module + // Add the allowed callers to the access module _setAccessControlForACaller(requester, caller); _setAccessControlForACaller(proposer, caller); _setAccessControlForACaller(disputer, caller); @@ -170,8 +170,8 @@ contract IntegrationBase is TestConstants, Helpers { vm.startPrank(_delegator); address[] memory _allowedCallers = new address[](1); _allowedCallers[0] = _caller; - _accessControlModule.setHasAccess(_allowedCallers); - oracle.setAccessControlModule(address(_accessControlModule), true); + _accessModule.setHasAccess(_allowedCallers); + oracle.setAccessModule(address(_accessModule), true); vm.stopPrank(); } } diff --git a/solidity/test/integration/ResponseDispute.t.sol b/solidity/test/integration/ResponseDispute.t.sol index c2cab00..5e1b76b 100644 --- a/solidity/test/integration/ResponseDispute.t.sol +++ b/solidity/test/integration/ResponseDispute.t.sol @@ -46,7 +46,9 @@ contract Integration_ResponseDispute is IntegrationBase { mockAccessControl.user = _anotherDisputer; // Revert if the response is access control is not approved - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert( + abi.encodeWithSelector(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector) + ); oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); // Revert if the response is already disputed diff --git a/solidity/test/integration/ResponseProposal.t.sol b/solidity/test/integration/ResponseProposal.t.sol index e6311d0..34a67de 100644 --- a/solidity/test/integration/ResponseProposal.t.sol +++ b/solidity/test/integration/ResponseProposal.t.sol @@ -15,19 +15,32 @@ contract Integration_ResponseProposal is IntegrationBase { mockAccessControl.user = _badRequester; // Revert if not approved to create request - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert( + abi.encodeWithSelector(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector) + ); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); vm.stopPrank(); // Revert if has no access to create request mockAccessControl.user = requester; + vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); vm.prank(badCaller); oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); - // Create request + // Revert if access control is an EOA + address _eoaAccessModule = makeAddr('eoaAccessModule'); + vm.prank(requester); + oracle.setAccessModule(address(_eoaAccessModule), true); + vm.startPrank(caller); + mockRequest.accessModule = _eoaAccessModule; + vm.expectRevert(); + oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); + + // Create request + mockRequest.accessModule = address(_accessModule); _requestId = oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); } @@ -35,7 +48,9 @@ contract Integration_ResponseProposal is IntegrationBase { mockResponse.response = _response; // Revert if not approved to create request - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleNotApproved.selector)); + vm.expectRevert( + abi.encodeWithSelector(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector) + ); address _badProposer = makeAddr('badProposer'); mockAccessControl.user = _badProposer; oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); diff --git a/solidity/test/mocks/contracts/MockAccessControlModule.sol b/solidity/test/mocks/contracts/MockAccessModule.sol similarity index 55% rename from solidity/test/mocks/contracts/MockAccessControlModule.sol rename to solidity/test/mocks/contracts/MockAccessModule.sol index 94dd219..8d77ccc 100644 --- a/solidity/test/mocks/contracts/MockAccessControlModule.sol +++ b/solidity/test/mocks/contracts/MockAccessModule.sol @@ -4,9 +4,9 @@ pragma solidity ^0.8.19; import {Module} from '../../../contracts/Module.sol'; import {IOracle} from '../../../interfaces/IOracle.sol'; -import {IAccessControlModule, IMockAccessControlModule} from '../interfaces/IMockAccessControlModule.sol'; +import {IAccessModule, IMockAccessModule} from '../interfaces/IMockAccessModule.sol'; -contract MockAccessControlModule is Module, IMockAccessControlModule { +contract MockAccessModule is Module, IMockAccessModule { mapping(address _caller => bool _hasAccess) public callerHasAccess; constructor(IOracle _oracle) Module(_oracle) {} @@ -19,8 +19,8 @@ contract MockAccessControlModule is Module, IMockAccessControlModule { function moduleName() external view returns (string memory _moduleName) {} - function hasAccess(bytes memory _data) external view override returns (bool _hasAccess) { - IAccessControlModule.AccessControlParameters memory _accessControlData = decodeAccesControlData(_data); + function hasAccess(bytes memory _data) external view returns (bool _hasAccess) { + IAccessModule.AccessControlParameters memory _accessControlData = decodeAccesControlData(_data); _hasAccess = callerHasAccess[_accessControlData.sender]; } @@ -28,8 +28,8 @@ contract MockAccessControlModule is Module, IMockAccessControlModule { public pure override - returns (IAccessControlModule.AccessControlParameters memory _accessControlData) + returns (IAccessModule.AccessControlParameters memory _accessControlData) { - _accessControlData = abi.decode(_data, (IAccessControlModule.AccessControlParameters)); + _accessControlData = abi.decode(_data, (IAccessModule.AccessControlParameters)); } } diff --git a/solidity/test/mocks/interfaces/IMockAccessControlModule.sol b/solidity/test/mocks/interfaces/IMockAccessModule.sol similarity index 55% rename from solidity/test/mocks/interfaces/IMockAccessControlModule.sol rename to solidity/test/mocks/interfaces/IMockAccessModule.sol index fb59a5c..e3ff836 100644 --- a/solidity/test/mocks/interfaces/IMockAccessControlModule.sol +++ b/solidity/test/mocks/interfaces/IMockAccessModule.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IAccessControlModule} from '../../../interfaces/modules/accessControl/IAccessControlModule.sol'; +import {IAccessModule} from '../../../interfaces/modules/access/IAccessModule.sol'; -interface IMockAccessControlModule is IAccessControlModule { +interface IMockAccessModule is IAccessModule { function decodeAccesControlData(bytes calldata _data) external view diff --git a/solidity/test/unit/AccessController.t.sol b/solidity/test/unit/AccessController.t.sol index 8dcb01a..e226b33 100644 --- a/solidity/test/unit/AccessController.t.sol +++ b/solidity/test/unit/AccessController.t.sol @@ -3,18 +3,19 @@ pragma solidity ^0.8.19; import 'forge-std/Test.sol'; -import {AccessController, IAccessControlModule, IAccessController} from '../../contracts/AccessController.sol'; +import {CommonAccessController, OracleAccessController} from '../../contracts/OracleAccessController.sol'; +import {IAccessController, IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; import {Helpers} from '../utils/Helpers.sol'; -contract MockAccessControl is AccessController { - constructor() AccessController() {} +contract MockAccessControl is OracleAccessController { + constructor() OracleAccessController() {} function hasAccessForTest( - address _accessControlModule, + address _accessModule, bytes32 _typehash, bytes memory _params, AccessControl memory _accessControl - ) public hasAccess(_accessControlModule, _typehash, _params, _accessControl) {} + ) public hasAccess(_accessModule, _typehash, _params, _accessControl) {} } /** @@ -37,13 +38,13 @@ contract BaseTest is Test, Helpers { contract AccessController_Unit_HasAccess is BaseTest { modifier happyPath( - address _accessControlModule, + address _accessModule, bytes32 _typehash, bytes memory _params, IAccessController.AccessControl memory _accessControl ) { vm.assume(_accessControl.user != caller); - vm.assume(_accessControlModule != address(0) && _accessControlModule != caller); + vm.assume(_accessModule != address(0) && _accessModule != caller); vm.assume(_params.length < 32); vm.startPrank(caller); @@ -51,22 +52,22 @@ contract AccessController_Unit_HasAccess is BaseTest { } function test_revertIfNoHasAccess( - address _accessControlModule, + address _accessModule, bytes32 _typehash, bytes memory _params, IAccessController.AccessControl memory _accessControl - ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { - IAccessControlModule.AccessControlParameters memory _expectedParams = IAccessControlModule.AccessControlParameters({ + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + IAccessModule.AccessControlParameters memory _expectedParams = IAccessModule.AccessControlParameters({ sender: caller, accessControl: _accessControl, typehash: _typehash, - params: _params + typehashParams: _params }); // Expect the hasAccess function to not be called _mockAndExpect( - _accessControlModule, - abi.encodeWithSelector(IAccessControlModule.hasAccess.selector, abi.encode(_expectedParams)), + _accessModule, + abi.encodeWithSelector(IAccessModule.hasAccess.selector, abi.encode(_expectedParams)), abi.encode(false) ); @@ -74,15 +75,15 @@ contract AccessController_Unit_HasAccess is BaseTest { vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); // Call the hasAccess function - accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); + accessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); } function test_revertIfAddressZero( - address _accessControlModule, + address _accessModule, bytes32 _typehash, bytes memory _params, IAccessController.AccessControl memory _accessControl - ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { // Expect the revert vm.expectRevert(abi.encodeWithSelector(IAccessController.AccessController_NoAccess.selector)); @@ -91,37 +92,37 @@ contract AccessController_Unit_HasAccess is BaseTest { } function test_hasAccessSenderIsNotUser( - address _accessControlModule, + address _accessModule, bytes32 _typehash, bytes memory _params, IAccessController.AccessControl memory _accessControl - ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { - IAccessControlModule.AccessControlParameters memory _expectedParams = IAccessControlModule.AccessControlParameters({ + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + IAccessModule.AccessControlParameters memory _expectedParams = IAccessModule.AccessControlParameters({ sender: caller, accessControl: _accessControl, typehash: _typehash, - params: _params + typehashParams: _params }); // Expect the hasAccess function to be called _mockAndExpect( - _accessControlModule, - abi.encodeWithSelector(IAccessControlModule.hasAccess.selector, abi.encode(_expectedParams)), + _accessModule, + abi.encodeWithSelector(IAccessModule.hasAccess.selector, abi.encode(_expectedParams)), abi.encode(true) ); // Call the hasAccess function - accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); + accessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); } function test_hasAccessSenderIsUser( - address _accessControlModule, + address _accessModule, bytes32 _typehash, bytes memory _params, IAccessController.AccessControl memory _accessControl - ) public happyPath(_accessControlModule, _typehash, _params, _accessControl) { + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { _accessControl.user = caller; // Call the hasAccess function - accessController.hasAccessForTest(_accessControlModule, _typehash, _params, _accessControl); + accessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); } } diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 216f3a2..6b7084b 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -8,8 +8,8 @@ import {IOracle} from '../../interfaces/IOracle.sol'; import {IDisputeModule} from '../../interfaces/modules/dispute/IDisputeModule.sol'; -import {IAccessController} from '../../interfaces/IAccessController.sol'; -import {IAccessControlModule} from '../../interfaces/modules/accessControl/IAccessControlModule.sol'; +import {IAccessController, IOracleAccessController} from '../../interfaces/IOracleAccessController.sol'; +import {IAccessModule} from '../../interfaces/modules/access/IAccessModule.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'; @@ -74,8 +74,8 @@ contract MockOracle is Oracle { _responseIds[_requestId] = abi.encodePacked(_responseIds[_requestId], _responseId); } - function mock_setAccessControlApproved(address _user, address _accessControlModule, bool _approved) external { - isAccessControlApproved[_user][_accessControlModule] = _approved; + function mock_setAccessModuleApproved(address _user, address _accessModule, bool _approved) external { + isAccessModuleApproved[_user][_accessModule] = _approved; } } @@ -92,7 +92,7 @@ contract BaseTest is Test, Helpers { IDisputeModule public disputeModule = IDisputeModule(_mockContract('disputeModule')); IResolutionModule public resolutionModule = IResolutionModule(_mockContract('resolutionModule')); IFinalityModule public finalityModule = IFinalityModule(_mockContract('finalityModule')); - IAccessControlModule public accessControlModule = IAccessControlModule(_mockContract('accessControlModule')); + IAccessModule public accessModule = IAccessModule(_mockContract('accessModule')); // Mock IPFS hash bytes32 internal _ipfsHash = bytes32('QmR4uiJH654k3Ta2uLLQ8r'); @@ -105,7 +105,9 @@ contract BaseTest is Test, Helpers { event DisputeEscalated(address indexed _caller, bytes32 indexed _disputeId, IOracle.Dispute _dispute); event DisputeStatusUpdated(bytes32 indexed _disputeId, IOracle.Dispute _dispute, IOracle.DisputeStatus _status); event DisputeResolved(bytes32 indexed _disputeId, IOracle.Dispute _dispute); - event AccessControlModuleSet(address indexed _user, address indexed _accessControlModule, bool _approved); + event AccessModuleSet(address indexed _user, address indexed _accessModule, bool _approved); + + address public anyone = makeAddr('anyone'); function setUp() public virtual { oracle = new MockOracle(); @@ -115,7 +117,7 @@ contract BaseTest is Test, Helpers { mockRequest.disputeModule = address(disputeModule); mockRequest.resolutionModule = address(resolutionModule); mockRequest.finalityModule = address(finalityModule); - mockRequest.accessControlModule = address(accessControlModule); + mockRequest.accessModule = address(accessModule); mockResponse.requestId = _getId(mockRequest); mockDispute.requestId = mockResponse.requestId; @@ -138,7 +140,7 @@ contract Oracle_Unit_CreateRequest is BaseTest { modifier happyPath() { mockAccessControl.user = requester; vm.startPrank(requester); - oracle.mock_setAccessControlApproved(requester, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(requester, address(accessModule), true); _; } /** @@ -202,23 +204,23 @@ contract Oracle_Unit_CreateRequest is BaseTest { } /** - * @notice Check that creating a request with a non-approved access control module reverts + * @notice Check that creating a request with a non-approved access module reverts */ function test_createRequest_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + vm.expectRevert(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector); // Test: try to create the request oracle.createRequest(mockRequest, _ipfsHash, mockAccessControl); } /** - * @notice Check that reverts if the access control module returns false + * @notice Check that reverts if the access module returns false */ function test_createRequest_revertsIfInvalidAccessControlData(address _caller) public { vm.assume(_caller != requester); - mockRequest.accessControlModule = address(0); + mockRequest.accessModule = address(0); mockAccessControl.user = requester; // Check: revert? @@ -280,7 +282,7 @@ contract Oracle_Unit_CreateRequests is BaseTest { IAccessController.AccessControl[] memory _accessControls = new IAccessController.AccessControl[](_requestsAmount); vm.startPrank(requester); - oracle.mock_setAccessControlApproved(requester, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(requester, address(accessModule), true); // Generate requests batch for (uint256 _i = 0; _i < _requestsAmount; _i++) { @@ -364,7 +366,7 @@ contract Oracle_Unit_CreateRequests is BaseTest { } vm.startPrank(requester); - oracle.mock_setAccessControlApproved(requester, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(requester, address(accessModule), true); oracle.createRequests(_requests, _ipfsHashes, _accessControls); uint256 _newNonce = oracle.totalRequestCount(); @@ -453,7 +455,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { modifier happyPath() { mockAccessControl.user = proposer; vm.startPrank(proposer); - oracle.mock_setAccessControlApproved(proposer, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(proposer, address(accessModule), true); _; } /** @@ -508,11 +510,11 @@ contract Oracle_Unit_ProposeResponse is BaseTest { } /** - * @notice Check that proposing a response with a non-approved access control module reverts + * @notice Check that proposing a response with a non-approved access module reverts */ function test_proposeResponse_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + vm.expectRevert(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector); // Test: try to create the request oracle.proposeResponse(mockRequest, mockResponse, mockAccessControl); @@ -527,12 +529,12 @@ contract Oracle_Unit_ProposeResponse is BaseTest { } /** - * @notice Revert if the access control module returns false + * @notice Revert if the access module returns false */ function test_proposeResponse_revertsIfInvalidAccessControlData(address _caller) public { vm.assume(_caller != proposer); - mockRequest.accessControlModule = address(0); + mockRequest.accessModule = address(0); mockAccessControl.user = proposer; // Check: revert? @@ -551,7 +553,7 @@ contract Oracle_Unit_ProposeResponse is BaseTest { oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); - oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(_caller, address(accessModule), true); mockAccessControl.user = _caller; // Check: revert? @@ -612,7 +614,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { modifier happyPath() { mockAccessControl.user = disputer; vm.startPrank(disputer); - oracle.mock_setAccessControlApproved(disputer, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(disputer, address(accessModule), true); _; } @@ -646,11 +648,11 @@ contract Oracle_Unit_DisputeResponse is BaseTest { } /** - * @notice Check that dispute a response with a non-approved access control module reverts + * @notice Check that dispute a response with a non-approved access module reverts */ function test_disputeResponse_revertsIfNotApproved() public { // Check: revert? - vm.expectRevert(IOracle.Oracle_AccessControlModuleNotApproved.selector); + vm.expectRevert(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector); // Test: try to create the request oracle.disputeResponse(mockRequest, mockResponse, mockDispute, mockAccessControl); @@ -686,11 +688,11 @@ contract Oracle_Unit_DisputeResponse is BaseTest { } /** - * @notice Revert if the access control module returns false + * @notice Revert if the access module returns false */ function test_disputeResponse_revertsIfInvalidAccessControlData(address _caller) public { vm.assume(_caller != disputer); - mockRequest.accessControlModule = address(0); + mockRequest.accessModule = address(0); mockAccessControl.user = disputer; // Check: revert? @@ -707,7 +709,7 @@ contract Oracle_Unit_DisputeResponse is BaseTest { function test_disputeResponse_revertIfWrongDisputer(address _caller) public { vm.assume(_caller != disputer); - oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(_caller, address(accessModule), true); mockAccessControl.user = _caller; // Check: revert? @@ -809,11 +811,9 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { // Mock the dispute oracle.mock_setDisputeOf(_responseId, _disputeId); oracle.mock_setDisputeCreatedAt(_disputeId, block.timestamp); - oracle.mock_setAccessControlApproved(proposer, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(proposer, address(accessModule), true); - vm.mockCall( - address(accessControlModule), abi.encodeWithSelector(IAccessControlModule.hasAccess.selector), abi.encode(true) - ); + vm.mockCall(address(accessModule), abi.encodeWithSelector(IAccessModule.hasAccess.selector), abi.encode(true)); // Check: revert? vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_NotDisputeOrResolutionModule.selector, proposer)); @@ -830,7 +830,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { bytes32 _disputeId = _getId(mockDispute); oracle.mock_setDisputeCreatedAt(_disputeId, 0); - oracle.mock_setAccessControlApproved(address(resolutionModule), address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(address(resolutionModule), address(accessModule), true); // Check: revert? vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_InvalidDispute.selector)); @@ -844,7 +844,7 @@ contract Oracle_Unit_UpdateDisputeStatus is BaseTest { contract Oracle_Unit_ResolveDispute is BaseTest { modifier happyPath() { mockAccessControl.user = address(resolutionModule); - oracle.mock_setAccessControlApproved(address(resolutionModule), address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(address(resolutionModule), address(accessModule), true); vm.startPrank(address(resolutionModule)); _; } @@ -1022,7 +1022,7 @@ contract Oracle_Unit_Finalize is BaseTest { modifier happyPath() { mockAccessControl.user = address(requester); - oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(mockAccessControl.user, address(accessModule), true); vm.startPrank(address(requester)); _; } @@ -1040,7 +1040,7 @@ contract Oracle_Unit_Finalize is BaseTest { bytes32 _responseId = _getId(mockResponse); mockAccessControl.user = _caller; - oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(mockAccessControl.user, address(accessModule), true); oracle.mock_addResponseId(_requestId, _responseId); oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); @@ -1081,7 +1081,7 @@ contract Oracle_Unit_Finalize is BaseTest { function test_finalize_revertsIfInvalidAccessControlData(address _caller) public { vm.assume(_caller != address(requester)); - mockRequest.accessControlModule = address(0); + mockRequest.accessModule = address(0); mockAccessControl.user = address(requester); // Check: revert? @@ -1183,7 +1183,7 @@ contract Oracle_Unit_Finalize is BaseTest { oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); mockResponse.requestId = bytes32(0); mockAccessControl.user = _caller; - oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(mockAccessControl.user, address(accessModule), true); // Create mock request and store it bytes memory _calldata = abi.encodeCall(IModule.finalizeRequest, (mockRequest, mockResponse, _caller)); @@ -1227,7 +1227,7 @@ contract Oracle_Unit_Finalize is BaseTest { oracle.mock_setRequestCreatedAt(_requestId, block.timestamp); mockAccessControl.user = _caller; - oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(mockAccessControl.user, address(accessModule), true); IOracle.DisputeStatus _disputeStatus = IOracle.DisputeStatus(_status); @@ -1268,7 +1268,7 @@ contract Oracle_Unit_Finalize is BaseTest { oracle.mock_setRequestCreatedAt(_getId(mockRequest), block.timestamp); mockAccessControl.user = _caller; - oracle.mock_setAccessControlApproved(mockAccessControl.user, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(mockAccessControl.user, address(accessModule), true); // Test: finalize a finalized request vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AlreadyFinalized.selector, _requestId)); @@ -1304,7 +1304,7 @@ contract Oracle_Unit_EscalateDispute is BaseTest { modifier happyPath(address _caller) { mockAccessControl.user = _caller; vm.startPrank(_caller); - oracle.mock_setAccessControlApproved(_caller, address(accessControlModule), true); + oracle.mock_setAccessModuleApproved(_caller, address(accessModule), true); _; } /** @@ -1426,30 +1426,34 @@ contract Oracle_Unit_EscalateDispute is BaseTest { } } -contract Oracle_Unit_SetAccessControlModule is BaseTest { - function test_revertIfAccessControlModuleAlreadySet(address _accessControlModule, bool _approved) public { +contract Oracle_Unit_SetAccessModule is BaseTest { + function test_revertIfAccessModuleAlreadySet(address _accessModule, bool _approved) public { vm.assume(_approved == true); - // Set the access control module - oracle.mock_setAccessControlApproved(address(this), _accessControlModule, _approved); + // Set the access module + oracle.mock_setAccessModuleApproved(address(this), _accessModule, _approved); - vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_AccessControlModuleAlreadySet.selector)); + vm.expectRevert( + abi.encodeWithSelector(IOracleAccessController.OracleAccessController_AccessModuleAlreadySet.selector) + ); - // Test: set the access control module - oracle.setAccessControlModule(address(_accessControlModule), _approved); + // Test: set the access module + oracle.setAccessModule(address(_accessModule), _approved); } - function test_setAccessControlModuleTrue(address _accessControlModule, bool _approved) public { + function test_setAccessModuleTrue(address _accessModule, bool _approved) public { vm.assume(_approved == true); - // Check: emits AccessControlModuleSet event? + vm.startPrank(anyone); + + // Check: emits AccessModuleSet event? _expectEmit(address(oracle)); - emit AccessControlModuleSet(address(this), address(_accessControlModule), _approved); + emit AccessModuleSet(anyone, address(_accessModule), _approved); - // Test: set the access control module - oracle.setAccessControlModule(address(_accessControlModule), _approved); + // Test: set the access module + oracle.setAccessModule(address(_accessModule), _approved); - // Check: correct access control module set? - assertEq(oracle.isAccessControlApproved(address(this), address(_accessControlModule)), _approved); + // Check: correct access module set? + assertEq(oracle.isAccessModuleApproved(anyone, address(_accessModule)), _approved); } } diff --git a/solidity/test/utils/Helpers.sol b/solidity/test/utils/Helpers.sol index 171e1ef..0e79edb 100644 --- a/solidity/test/utils/Helpers.sol +++ b/solidity/test/utils/Helpers.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -import {IAccessController} from '../../contracts/AccessController.sol'; import {IOracle} from '../../contracts/Oracle.sol'; +import {IAccessController} from '../../interfaces/IAccessController.sol'; import {Test} from 'forge-std/Test.sol'; contract Helpers is Test { From 0b8a5bec29bde1d7e085968e0c1434ded40a8c9c Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sun, 10 Nov 2024 20:29:58 +0400 Subject: [PATCH 14/19] refactor: merge `hasAccess` and `isApproved` modifiers --- solidity/contracts/Oracle.sol | 14 ++------ solidity/contracts/OracleAccessController.sol | 13 ++------ solidity/test/unit/AccessController.t.sol | 33 +++++++++++++++++-- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index f4719f2..7598942 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -3,9 +3,8 @@ pragma solidity ^0.8.19; import {IOracle} from '../interfaces/IOracle.sol'; -import {IDisputeModule} from '../interfaces/modules/dispute/IDisputeModule.sol'; - import {IAccessModule} from '../interfaces/modules/access/IAccessModule.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'; @@ -129,7 +128,6 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessModule) hasAccess(_request.accessModule, PROPOSE_TYPEHASH, abi.encode(_request, _response), _accessControl) returns (bytes32 _responseId) { @@ -170,7 +168,6 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessModule) hasAccess(_request.accessModule, DISPUTE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) returns (bytes32 _disputeId) { @@ -216,7 +213,6 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessModule) hasAccess(_request.accessModule, ESCALATE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -255,7 +251,6 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { AccessControl calldata _accessControl ) external - isApproved(_accessControl.user, _request.accessModule) hasAccess(_request.accessModule, RESOLVE_TYPEHASH, abi.encode(_request, _response, _dispute), _accessControl) { (bytes32 _responseId, bytes32 _disputeId) = ValidatorLib._validateResponseAndDispute(_request, _response, _dispute); @@ -321,11 +316,7 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { IOracle.Request calldata _request, IOracle.Response calldata _response, AccessControl calldata _accessControl - ) - external - isApproved(_accessControl.user, _request.accessModule) - hasAccess(_request.accessModule, FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) - { + ) external hasAccess(_request.accessModule, FINALIZE_TYPEHASH, abi.encode(_request, _response), _accessControl) { bytes32 _requestId; bytes32 _responseId; @@ -438,7 +429,6 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { AccessControl calldata _accessControl ) internal - isApproved(_accessControl.user, _request.accessModule) hasAccess(_request.accessModule, CREATE_TYPEHASH, abi.encode(_request), _accessControl) returns (bytes32 _requestId) { diff --git a/solidity/contracts/OracleAccessController.sol b/solidity/contracts/OracleAccessController.sol index 42ebfea..a9251fa 100644 --- a/solidity/contracts/OracleAccessController.sol +++ b/solidity/contracts/OracleAccessController.sol @@ -14,19 +14,10 @@ abstract contract OracleAccessController is IOracleAccessController, CommonAcces bytes memory _params, AccessControl memory _accessControl ) { - _hasAccess(_accessModule, _typehash, _params, _accessControl); - _; - } - - /** - * @notice Modifier to check if the user approved to the access module - * @param _user The address of setAccessModulethe user - * @param _accessModule The access module to check if approved - */ - modifier isApproved(address _user, address _accessModule) { - if (_accessModule != address(0) && !isAccessModuleApproved[_user][_accessModule]) { + if (_accessModule != address(0) && !isAccessModuleApproved[_accessControl.user][_accessModule]) { revert OracleAccessController_AccessModuleNotApproved(); } + _hasAccess(_accessModule, _typehash, _params, _accessControl); _; } diff --git a/solidity/test/unit/AccessController.t.sol b/solidity/test/unit/AccessController.t.sol index e226b33..0bf827e 100644 --- a/solidity/test/unit/AccessController.t.sol +++ b/solidity/test/unit/AccessController.t.sol @@ -3,7 +3,11 @@ pragma solidity ^0.8.19; import 'forge-std/Test.sol'; -import {CommonAccessController, OracleAccessController} from '../../contracts/OracleAccessController.sol'; +import { + CommonAccessController, + IOracleAccessController, + OracleAccessController +} from '../../contracts/OracleAccessController.sol'; import {IAccessController, IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; import {Helpers} from '../utils/Helpers.sol'; @@ -16,6 +20,10 @@ contract MockAccessControl is OracleAccessController { bytes memory _params, AccessControl memory _accessControl ) public hasAccess(_accessModule, _typehash, _params, _accessControl) {} + + function setAccessModuleForTest(address _user, address _accessModule, bool _approved) public { + isAccessModuleApproved[_user][_accessModule] = _approved; + } } /** @@ -46,8 +54,10 @@ contract AccessController_Unit_HasAccess is BaseTest { vm.assume(_accessControl.user != caller); vm.assume(_accessModule != address(0) && _accessModule != caller); vm.assume(_params.length < 32); - vm.startPrank(caller); + accessController.setAccessModuleForTest(_accessControl.user, _accessModule, true); + + vm.startPrank(caller); _; } @@ -91,6 +101,24 @@ contract AccessController_Unit_HasAccess is BaseTest { accessController.hasAccessForTest(address(0), _typehash, _params, _accessControl); } + function test_revertIfAccessModuleNotApproved( + address _accessModule, + bytes32 _typehash, + bytes memory _params, + IAccessController.AccessControl memory _accessControl + ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + // Ensure the access module is not approved + accessController.setAccessModuleForTest(_accessControl.user, _accessModule, false); + + // Expect the revert + vm.expectRevert( + abi.encodeWithSelector(IOracleAccessController.OracleAccessController_AccessModuleNotApproved.selector) + ); + + // Call the hasAccess function + accessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); + } + function test_hasAccessSenderIsNotUser( address _accessModule, bytes32 _typehash, @@ -121,6 +149,7 @@ contract AccessController_Unit_HasAccess is BaseTest { bytes memory _params, IAccessController.AccessControl memory _accessControl ) public happyPath(_accessModule, _typehash, _params, _accessControl) { + accessController.setAccessModuleForTest(caller, _accessModule, true); _accessControl.user = caller; // Call the hasAccess function accessController.hasAccessForTest(_accessModule, _typehash, _params, _accessControl); From 8bb062e07e20e54b6e444a62c4a46d0c4a4c7c87 Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Sun, 10 Nov 2024 20:51:11 +0400 Subject: [PATCH 15/19] test: explicit mocking in Validator unit tests --- solidity/test/unit/Validator.t.sol | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/solidity/test/unit/Validator.t.sol b/solidity/test/unit/Validator.t.sol index 3a69d68..af839f2 100644 --- a/solidity/test/unit/Validator.t.sol +++ b/solidity/test/unit/Validator.t.sol @@ -71,7 +71,7 @@ contract MockValidator is Validator { */ contract BaseTest is Test, Helpers { // Mock Oracle - IOracle public oracle = IOracle(_mockContract('oracle')); + IOracle public oracle = IOracle(_mockContract('Oracle')); // The target contract MockValidator public validator; @@ -95,6 +95,18 @@ contract BaseTest is Test, Helpers { mockResponse.requestId = _getId(mockRequest); mockDispute.requestId = mockResponse.requestId; mockDispute.responseId = _getId(mockResponse); + + vm.mockCall( + address(oracle), + abi.encodeWithSelector(IOracle.disputeCreatedAt.selector, _getId(mockDispute)), + abi.encode(mockDispute) + ); + + vm.mockCall( + address(oracle), + abi.encodeWithSelector(IOracle.responseCreatedAt.selector, _getId(mockResponse)), + abi.encode(mockResponse) + ); } } From 47120e07690d5a939a34b3e0c86e8de95d546d02 Mon Sep 17 00:00:00 2001 From: Ashitaka Date: Mon, 11 Nov 2024 14:25:43 -0300 Subject: [PATCH 16/19] fix: typo --- solidity/test/unit/CommonAccessController.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/test/unit/CommonAccessController.t.sol b/solidity/test/unit/CommonAccessController.t.sol index 9b64cf9..e4e9b37 100644 --- a/solidity/test/unit/CommonAccessController.t.sol +++ b/solidity/test/unit/CommonAccessController.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.19; import 'forge-std/Test.sol'; -import {CommonAccessController, IAccessController} from '../../contracts/commonAccessController.sol'; +import {CommonAccessController, IAccessController} from '../../contracts/CommonAccessController.sol'; import {IAccessController, IAccessModule} from '../../interfaces/modules/access/IAccessModule.sol'; import {Helpers} from '../utils/Helpers.sol'; From d34d4baf68d32ef1bd70210f55dfde8d9a0f37ea Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:07:10 +0400 Subject: [PATCH 17/19] refactor: eliminate extra variables --- solidity/contracts/Oracle.sol | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 7e9e84c..96c1bfe 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -269,20 +269,13 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { revert Oracle_CannotResolve(_disputeId); } - // Needed to avoid stack too deep error - Request memory _usedRequest = _request; - Response memory _usedResponse = _response; - Dispute memory _usedDispute = _dispute; - - if (_usedRequest.resolutionModule == address(0)) { + if (_request.resolutionModule == address(0)) { revert Oracle_NoResolutionModule(_disputeId); } - IResolutionModule(_usedRequest.resolutionModule).resolveDispute( - _disputeId, _usedRequest, _usedResponse, _usedDispute - ); + IResolutionModule(_request.resolutionModule).resolveDispute(_disputeId, _request, _response, _dispute); - emit DisputeResolved(_disputeId, _usedDispute); + emit DisputeResolved(_disputeId, _dispute); } /// @inheritdoc IOracle From ba7cf26e30831fd9e250256c44cfb9f277508aaa Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:08:22 +0400 Subject: [PATCH 18/19] fix: duplicated finalize call --- solidity/contracts/Oracle.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 96c1bfe..be79396 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -337,7 +337,6 @@ contract Oracle is IOracle, OracleAccessController, OracleTypehash { IDisputeModule(_request.disputeModule).finalizeRequest(_request, _response, _accessControl.user); IResponseModule(_request.responseModule).finalizeRequest(_request, _response, _accessControl.user); IRequestModule(_request.requestModule).finalizeRequest(_request, _response, _accessControl.user); - IAccessModule(_request.accessModule).finalizeRequest(_request, _response, _accessControl.user); if (_request.accessModule != address(0)) { IAccessModule(_request.accessModule).finalizeRequest(_request, _response, _accessControl.user); From 1d1e5a38da02c1d704a773bdb147dd31f3010f6b Mon Sep 17 00:00:00 2001 From: Gas One Cent <86567384+gas1cent@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:09:56 +0400 Subject: [PATCH 19/19] docs: natspec fixes --- solidity/interfaces/IOracleAccessController.sol | 2 +- solidity/interfaces/modules/access/IAccessModule.sol | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/solidity/interfaces/IOracleAccessController.sol b/solidity/interfaces/IOracleAccessController.sol index 7473de5..d44df90 100644 --- a/solidity/interfaces/IOracleAccessController.sol +++ b/solidity/interfaces/IOracleAccessController.sol @@ -52,7 +52,7 @@ interface IOracleAccessController is IAccessController { //////////////////////////////////////////////////////////////*/ /** - * @notice Sets the approval of an the access module address. + * @notice Sets the approval of an access module address. * * @param _accessModule The address of the access module * @param _approved True to approve, false otherwise. diff --git a/solidity/interfaces/modules/access/IAccessModule.sol b/solidity/interfaces/modules/access/IAccessModule.sol index 05c2592..90f156a 100644 --- a/solidity/interfaces/modules/access/IAccessModule.sol +++ b/solidity/interfaces/modules/access/IAccessModule.sol @@ -16,10 +16,9 @@ interface IAccessModule is IModule { /** * @notice Access control parameters * @param sender The address of the sender - * @param user The address of the user * @param typehash The typehash of the access control * @param data The data for access control validation - * @param params The parameters for access control validation + * @param typehashParams The parameters for access control validation */ struct AccessControlParameters { address sender;