diff --git a/docs/src/content/modules/resolution/arbitrator_module.md b/docs/src/content/modules/resolution/arbitrator_module.md index b0476171..dac2c04c 100644 --- a/docs/src/content/modules/resolution/arbitrator_module.md +++ b/docs/src/content/modules/resolution/arbitrator_module.md @@ -14,7 +14,7 @@ The Arbitrator Module is a part of the dispute resolution system. It allows an e - `isValid(bytes32 _disputeId)`: Indicates whether the dispute has been arbitrated. - `startResolution(bytes32 _disputeId)`: Starts the arbitration process by calling `resolve` on the arbitrator and flags the dispute as `Active`. - `resolveDispute(bytes32 _disputeId)`: Resolves the dispute by getting the answer from the arbitrator and notifying the oracle. -- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request. +- `decodeRequestData(bytes calldata _data)`: Returns the decoded data for a request. ### Request Parameters diff --git a/solidity/contracts/modules/resolution/ArbitratorModule.sol b/solidity/contracts/modules/resolution/ArbitratorModule.sol index daf419fc..0f4df46a 100644 --- a/solidity/contracts/modules/resolution/ArbitratorModule.sol +++ b/solidity/contracts/modules/resolution/ArbitratorModule.sol @@ -1,62 +1,69 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; - -// // solhint-disable-next-line no-unused-import -// import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol'; -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; - -// import {IArbitratorModule} from '../../../interfaces/modules/resolution/IArbitratorModule.sol'; -// import {IArbitrator} from '../../../interfaces/IArbitrator.sol'; - -// contract ArbitratorModule is Module, IArbitratorModule { -// /** -// * @notice The status of all disputes -// */ -// mapping(bytes32 _disputeId => ArbitrationStatus _status) internal _disputeData; - -// constructor(IOracle _oracle) Module(_oracle) {} - -// /// @inheritdoc IModule -// function moduleName() external pure returns (string memory _moduleName) { -// return 'ArbitratorModule'; -// } - -// /// @inheritdoc IArbitratorModule -// function decodeRequestData(bytes32 _requestId) public view returns (address _arbitrator) { -// _arbitrator = abi.decode(requestData[_requestId], (address)); -// } - -// /// @inheritdoc IArbitratorModule -// function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus) { -// _disputeStatus = _disputeData[_disputeId]; -// } - -// /// @inheritdoc IArbitratorModule -// function startResolution(bytes32 _disputeId) external onlyOracle { -// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId); - -// address _arbitrator = abi.decode(requestData[_dispute.requestId], (address)); -// if (_arbitrator == address(0)) revert ArbitratorModule_InvalidArbitrator(); - -// _disputeData[_disputeId] = ArbitrationStatus.Active; -// IArbitrator(_arbitrator).resolve(_disputeId); - -// emit ResolutionStarted(_dispute.requestId, _disputeId); -// } - -// /// @inheritdoc IArbitratorModule -// function resolveDispute(bytes32 _disputeId) external onlyOracle { -// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId); -// if (_dispute.status != IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidDisputeId(); - -// address _arbitrator = abi.decode(requestData[_dispute.requestId], (address)); -// IOracle.DisputeStatus _status = IArbitrator(_arbitrator).getAnswer(_disputeId); - -// if (_status <= IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidResolutionStatus(); -// _disputeData[_disputeId] = ArbitrationStatus.Resolved; - -// ORACLE.updateDisputeStatus(_disputeId, _status); - -// emit DisputeResolved(_dispute.requestId, _disputeId, _status); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// solhint-disable-next-line no-unused-import +import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; + +import {IArbitratorModule} from '../../../interfaces/modules/resolution/IArbitratorModule.sol'; +import {IArbitrator} from '../../../interfaces/IArbitrator.sol'; + +contract ArbitratorModule is Module, IArbitratorModule { + /** + * @notice The status of all disputes + */ + mapping(bytes32 _disputeId => ArbitrationStatus _status) internal _disputeData; + + constructor(IOracle _oracle) Module(_oracle) {} + + /// @inheritdoc IModule + function moduleName() external pure returns (string memory _moduleName) { + return 'ArbitratorModule'; + } + + /// @inheritdoc IArbitratorModule + function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) { + _params = abi.decode(_data, (RequestParameters)); + } + + /// @inheritdoc IArbitratorModule + function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus) { + _disputeStatus = _disputeData[_disputeId]; + } + + /// @inheritdoc IArbitratorModule + function startResolution( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData); + if (_params.arbitrator == address(0)) revert ArbitratorModule_InvalidArbitrator(); + + _disputeData[_disputeId] = ArbitrationStatus.Active; + IArbitrator(_params.arbitrator).resolve(_disputeId); + + emit ResolutionStarted(_dispute.requestId, _disputeId); + } + + /// @inheritdoc IArbitratorModule + function resolveDispute( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external onlyOracle { + if (ORACLE.disputeStatus(_disputeId) != IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidDisputeId(); + + RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData); + IOracle.DisputeStatus _status = IArbitrator(_params.arbitrator).getAnswer(_disputeId); + + if (_status <= IOracle.DisputeStatus.Escalated) revert ArbitratorModule_InvalidResolutionStatus(); + _disputeData[_disputeId] = ArbitrationStatus.Resolved; + + ORACLE.updateDisputeStatus(_request, _response, _dispute, _status); + + emit DisputeResolved(_dispute.requestId, _disputeId, _status); + } +} diff --git a/solidity/interfaces/modules/resolution/IArbitratorModule.sol b/solidity/interfaces/modules/resolution/IArbitratorModule.sol index b5bf211b..17e9747d 100644 --- a/solidity/interfaces/modules/resolution/IArbitratorModule.sol +++ b/solidity/interfaces/modules/resolution/IArbitratorModule.sol @@ -1,84 +1,106 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.19; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; -// import {IResolutionModule} from -// '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/resolution/IResolutionModule.sol'; +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IResolutionModule} from + '@defi-wonderland/prophet-core-contracts/solidity/interfaces/modules/resolution/IResolutionModule.sol'; -// /* -// * @title ArbitratorModule -// * @notice Module allowing an external arbitrator contract -// * to resolve a dispute. -// */ -// interface IArbitratorModule is IResolutionModule { -// /*/////////////////////////////////////////////////////////////// -// ERRORS -// //////////////////////////////////////////////////////////////*/ +/* + * @title ArbitratorModule + * @notice Module allowing an external arbitrator contract + * to resolve a dispute. + */ +interface IArbitratorModule is IResolutionModule { + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Thrown when an unauthorized caller calls a function only the arbitrator can call -// */ -// error ArbitratorModule_OnlyArbitrator(); + /** + * @notice Thrown when an unauthorized caller calls a function only the arbitrator can call + */ + error ArbitratorModule_OnlyArbitrator(); -// /** -// * @notice Thrown when trying to resolve a dispute that is not escalated -// */ -// error ArbitratorModule_InvalidDisputeId(); + /** + * @notice Thrown when trying to resolve a dispute that is not escalated + */ + error ArbitratorModule_InvalidDisputeId(); -// /** -// * @notice Thrown when the arbitrator address is the address zero -// */ -// error ArbitratorModule_InvalidArbitrator(); + /** + * @notice Thrown when the arbitrator address is the address zero + */ + error ArbitratorModule_InvalidArbitrator(); -// /** -// * @notice Thrown when the arbitrator returns an invalid resolution status -// */ -// error ArbitratorModule_InvalidResolutionStatus(); + /** + * @notice Thrown when the arbitrator returns an invalid resolution status + */ + error ArbitratorModule_InvalidResolutionStatus(); -// /*/////////////////////////////////////////////////////////////// -// ENUMS -// //////////////////////////////////////////////////////////////*/ + /*/////////////////////////////////////////////////////////////// + STRUCTS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Parameters of the request as stored in the module + * @param arbitrator The address of the arbitrator + */ + struct RequestParameters { + address arbitrator; + } -// /** -// * @notice Available status of the arbitration process -// */ -// enum ArbitrationStatus { -// Unknown, // The arbitration process has not started (default) -// Active, // The arbitration process is active -// Resolved // The arbitration process is resolved -// } + /*/////////////////////////////////////////////////////////////// + ENUMS + //////////////////////////////////////////////////////////////*/ -// /*/////////////////////////////////////////////////////////////// -// LOGIC -// //////////////////////////////////////////////////////////////*/ + /** + * @notice Available status of the arbitration process + */ + enum ArbitrationStatus { + Unknown, // The arbitration process has not started (default) + Active, // The arbitration process is active + Resolved // The arbitration process is resolved + } -// /** -// * @notice Returns the current arbitration status of a dispute -// * @param _disputeId The ID of the dispute -// * @return _disputeStatus The `ArbitrationStatus` of the dispute -// */ -// function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus); + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ -// /** -// * @notice Starts the arbitration process by calling `resolve` on the -// * arbitrator and flags the dispute as Active -// * @dev Only callable by the Oracle -// * @dev Will revert if the arbitrator address is the address zero -// * @param _disputeId The ID of the dispute -// */ -// function startResolution(bytes32 _disputeId) external; + /** + * @notice Returns the current arbitration status of a dispute + * @param _disputeId The ID of the dispute + * @return _disputeStatus The `ArbitrationStatus` of the dispute + */ + function getStatus(bytes32 _disputeId) external view returns (ArbitrationStatus _disputeStatus); -// /** -// * @notice Resolves the dispute by getting the answer from the arbitrator -// * and updating the dispute status -// * @dev Only callable by the Oracle -// * @param _disputeId The ID of the dispute -// */ -// function resolveDispute(bytes32 _disputeId) external; + /** + * @notice Starts the arbitration process by calling `resolve` on the + * arbitrator and flags the dispute as Active + * @dev Only callable by the Oracle + * @dev Will revert if the arbitrator address is the address zero + * @param _disputeId The ID of the dispute + */ + function startResolution( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; -// /** -// * @notice Returns the decoded data for a request -// * @param _requestId The ID of the request -// * @return _arbitrator The address of the arbitrator -// */ -// function decodeRequestData(bytes32 _requestId) external view returns (address _arbitrator); -// } + /** + * @notice Resolves the dispute by getting the answer from the arbitrator + * and updating the dispute status + * @dev Only callable by the Oracle + * @param _disputeId The ID of the dispute + */ + function resolveDispute( + bytes32 _disputeId, + IOracle.Request calldata _request, + IOracle.Response calldata _response, + IOracle.Dispute calldata _dispute + ) external; + + /** + * @notice Returns the decoded data for a request + * @param _data The encoded request parameters + * @return _params The struct containing the parameters for the request + */ + function decodeRequestData(bytes calldata _data) external view returns (RequestParameters memory _params); +} diff --git a/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol b/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol index 7f13db93..66584655 100644 --- a/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol +++ b/solidity/test/unit/modules/resolution/ArbitratorModule.t.sol @@ -1,328 +1,281 @@ -// // SPDX-License-Identifier: AGPL-3.0-only -// pragma solidity ^0.8.19; - -// import 'forge-std/Test.sol'; - -// import {Helpers} from '../../../utils/Helpers.sol'; - -// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; -// import {IModule} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IModule.sol'; - -// import { -// ArbitratorModule, -// IArbitratorModule, -// IArbitrator -// } from '../../../../contracts/modules/resolution/ArbitratorModule.sol'; - -// /** -// * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks -// */ -// contract ForTest_ArbitratorModule is ArbitratorModule { -// constructor(IOracle _oracle) ArbitratorModule(_oracle) {} - -// function forTest_setRequestData(bytes32 _requestId, bytes memory _data) public { -// requestData[_requestId] = _data; -// } - -// function forTest_setDisputeStatus(bytes32 _disputeId, IArbitratorModule.ArbitrationStatus _status) public { -// _disputeData[_disputeId] = _status; -// } -// } - -// /** -// * @title Arbitrator Module Unit tests -// */ -// contract BaseTest is Test, Helpers { -// // The target contract -// ForTest_ArbitratorModule public arbitratorModule; -// // A mock oracle -// IOracle public oracle; -// // A mock arbitrator -// IArbitrator public arbitrator; -// // Create a new dummy dispute -// IOracle.Dispute public mockDispute; - -// event ResolutionStarted(bytes32 indexed _requestId, bytes32 indexed _disputeId); -// event DisputeResolved(bytes32 indexed _requestId, bytes32 indexed _disputeId, IOracle.DisputeStatus _status); - -// /** -// * @notice Deploy the target and mock oracle -// */ -// function setUp() public { -// oracle = IOracle(makeAddr('Oracle')); -// vm.etch(address(oracle), hex'069420'); - -// arbitrator = IArbitrator(makeAddr('MockArbitrator')); -// vm.etch(address(arbitrator), hex'069420'); - -// arbitratorModule = new ForTest_ArbitratorModule(oracle); - -// mockDispute = IOracle.Dispute({ -// createdAt: block.timestamp, -// disputer: makeAddr('disputer'), -// proposer: makeAddr('proposer'), -// responseId: bytes32('69'), -// requestId: bytes32('69'), -// status: IOracle.DisputeStatus.Active -// }); -// } -// } - -// contract ArbitratorModule_Unit_ModuleData is BaseTest { -// /** -// * @notice Test that the moduleName function returns the correct name -// */ -// function test_moduleNameReturnsName() public { -// assertEq(arbitratorModule.moduleName(), 'ArbitratorModule'); -// } -// /** -// * @notice Test that the decodeRequestData function returns the correct values -// */ - -// function test_decodeRequestData(bytes32 _requestId, address _arbitrator) public { -// // Mock data -// bytes memory _requestData = abi.encode(address(_arbitrator)); - -// // Store the mock dispute -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Test: decode the given request data -// (address _arbitratorStored) = arbitratorModule.decodeRequestData(_requestId); - -// // Check: decoded values match original values? -// assertEq(_arbitratorStored, _arbitrator); -// } - -// /** -// * @notice Test that the status is correctly retrieved -// */ -// function test_getStatus(uint256 _status, bytes32 _disputeId) public { -// _status = bound(_status, 0, uint256(IArbitratorModule.ArbitrationStatus.Resolved)); -// IArbitratorModule.ArbitrationStatus _arbitratorStatus = IArbitratorModule.ArbitrationStatus(_status); - -// // Store the mock dispute -// arbitratorModule.forTest_setDisputeStatus(_disputeId, _arbitratorStatus); - -// // Check: The correct status is returned? -// assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(_status)); -// } -// } - -// contract ArbitratorModule_Unit_StartResolution is BaseTest { -// /** -// * @notice Test that the escalate function works as expected -// */ -// function test_startResolution(bytes32 _disputeId, bytes32 _requestId) public { -// // Mock and expect the dummy dispute -// mockDispute.requestId = _requestId; -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Store the requestData -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect the callback to the arbitrator -// _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); - -// vm.prank(address(oracle)); -// arbitratorModule.startResolution(_disputeId); - -// // Check: is status now Escalated? -// assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Active)); -// } - -// function test_emitsEvent(bytes32 _disputeId, bytes32 _requestId) public { -// // Mock and expect the dummy dispute -// mockDispute.requestId = _requestId; -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Store the requestData -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock and expect the callback to the arbitrator -// _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(arbitratorModule)); -// emit ResolutionStarted(_requestId, _disputeId); - -// vm.prank(address(oracle)); -// arbitratorModule.startResolution(_disputeId); -// } - -// function test_revertInvalidCaller(address _caller, bytes32 _disputeId) public { -// vm.assume(_caller != address(oracle)); - -// // Check: does it revert if the caller is not the Oracle? -// vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); - -// vm.prank(_caller); -// arbitratorModule.startResolution(_disputeId); -// } - -// function test_revertIfEmptyArbitrator(bytes32 _disputeId, bytes32 _requestId) public { -// // Mock and expect the dummy dispute -// mockDispute.requestId = _requestId; -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Store the requestData -// bytes memory _requestData = abi.encode(address(0)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Check: revert? -// vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidArbitrator.selector)); - -// // Test: escalate the dispute -// vm.prank(address(oracle)); -// arbitratorModule.startResolution(_disputeId); -// } -// } - -// contract ArbitratorModule_Unit_ResolveDispute is BaseTest { -// /** -// * @notice Test that the resolve function works as expected -// */ -// function test_resolveDispute(bytes32 _disputeId, bytes32 _requestId, uint256 _status) public { -// vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); -// vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); -// IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock the dummy dispute -// mockDispute.requestId = _requestId; -// mockDispute.status = IOracle.DisputeStatus.Escalated; - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Mock and expect getAnswer to be called on the arbitrator -// _mockAndExpect( -// address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) -// ); - -// // Mock and expect IOracle.updateDisputeStatus to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(oracle.updateDisputeStatus, (_disputeId, _arbitratorStatus)), abi.encode() -// ); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); - -// // Check: is status now Resolved? -// assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Resolved)); -// } - -// function test_revertsIfInvalidResolveStatus(bytes32 _disputeId, bytes32 _requestId, uint256 _status) public { -// vm.assume(_status <= uint256(IOracle.DisputeStatus.Escalated)); -// IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock the dummy dispute -// mockDispute.requestId = _requestId; -// mockDispute.status = IOracle.DisputeStatus.Escalated; - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Mock and expect getAnswer to be called on the arbitrator -// _mockAndExpect( -// address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) -// ); - -// // Check: does it revert if the resolution status is invalid? -// vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidResolutionStatus.selector)); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); -// } - -// function test_emitsEvent(bytes32 _disputeId, bytes32 _requestId, uint256 _status) public { -// vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); -// vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); -// IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Mock the dummy dispute -// mockDispute.requestId = _requestId; -// mockDispute.status = IOracle.DisputeStatus.Escalated; - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(mockDispute)); - -// // Mock and expect getAnswer to be called on the arbitrator -// _mockAndExpect( -// address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) -// ); - -// // Mock and expect IOracle.updateDisputeStatus to be called -// _mockAndExpect( -// address(oracle), abi.encodeCall(oracle.updateDisputeStatus, (_disputeId, _arbitratorStatus)), abi.encode() -// ); - -// // Check: is the event emitted? -// vm.expectEmit(true, true, true, true, address(arbitratorModule)); -// emit DisputeResolved(_requestId, _disputeId, _arbitratorStatus); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); -// } - -// /** -// * @notice resolve dispute reverts if the dispute status isn't Active -// */ -// function test_revertIfInvalidDispute(bytes32 _requestId, bytes32 _responseId, bytes32 _disputeId) public { -// // Store the requestData -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Test the 3 different invalid status (None, Won, Lost) -// for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { -// if (IOracle.DisputeStatus(_status) == IOracle.DisputeStatus.Escalated) continue; -// // Create a new dummy dispute -// IOracle.Dispute memory _dispute = IOracle.Dispute({ -// createdAt: block.timestamp, -// disputer: makeAddr('disputer'), -// proposer: makeAddr('proposer'), -// responseId: _responseId, -// requestId: _requestId, -// status: IOracle.DisputeStatus(_status) -// }); - -// // Mock and expect IOracle.getDispute to be called -// _mockAndExpect(address(oracle), abi.encodeCall(oracle.getDispute, (_disputeId)), abi.encode(_dispute)); - -// // Check: does it revert if the dispute id is invalid? -// vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidDisputeId.selector)); - -// vm.prank(address(oracle)); -// arbitratorModule.resolveDispute(_disputeId); -// } -// } - -// /** -// * @notice Test that the resolve function reverts if the caller isn't the arbitrator -// */ -// function test_revertIfWrongSender(bytes32 _disputeId, bytes32 _requestId, address _caller) public { -// vm.assume(_caller != address(oracle)); - -// // Store the mock dispute -// bytes memory _requestData = abi.encode(address(arbitrator)); -// arbitratorModule.forTest_setRequestData(_requestId, _requestData); - -// // Check: does it revert if not called by the Oracle? -// vm.expectRevert(IModule.Module_OnlyOracle.selector); - -// vm.prank(_caller); -// arbitratorModule.resolveDispute(_disputeId); -// } -// } +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +import {Helpers} from '../../../utils/Helpers.sol'; + +import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol'; +import {IModule} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IModule.sol'; + +import { + ArbitratorModule, + IArbitratorModule, + IArbitrator +} from '../../../../contracts/modules/resolution/ArbitratorModule.sol'; + +/** + * @dev Harness to set an entry in the requestData mapping, without triggering setup request hooks + */ +contract ForTest_ArbitratorModule is ArbitratorModule { + constructor(IOracle _oracle) ArbitratorModule(_oracle) {} + + function forTest_setDisputeStatus(bytes32 _disputeId, IArbitratorModule.ArbitrationStatus _status) public { + _disputeData[_disputeId] = _status; + } +} + +/** + * @title Arbitrator Module Unit tests + */ +contract BaseTest is Test, Helpers { + // The target contract + ForTest_ArbitratorModule public arbitratorModule; + // A mock oracle + IOracle public oracle; + // A mock arbitrator + IArbitrator public arbitrator; + // Create a new dummy dispute + IOracle.Dispute public mockDispute; + // Create a new dummy response + IOracle.Response public mockResponse; + address internal _proposer = makeAddr('proposer'); + bytes32 public mockId = bytes32('69'); + + event ResolutionStarted(bytes32 indexed _requestId, bytes32 indexed _disputeId); + event DisputeResolved(bytes32 indexed _requestId, bytes32 indexed _disputeId, IOracle.DisputeStatus _status); + + /** + * @notice Deploy the target and mock oracle + */ + function setUp() public { + oracle = IOracle(makeAddr('Oracle')); + vm.etch(address(oracle), hex'069420'); + + arbitrator = IArbitrator(makeAddr('MockArbitrator')); + vm.etch(address(arbitrator), hex'069420'); + + arbitratorModule = new ForTest_ArbitratorModule(oracle); + + mockDispute = IOracle.Dispute({ + disputer: makeAddr('disputer'), + proposer: makeAddr('proposer'), + responseId: bytes32('69'), + requestId: bytes32('69') + }); + + mockResponse = IOracle.Response({proposer: _proposer, requestId: mockId, response: bytes('')}); + } +} + +contract ArbitratorModule_Unit_ModuleData is BaseTest { + /** + * @notice Test that the moduleName function returns the correct name + */ + function test_moduleNameReturnsName() public { + assertEq(arbitratorModule.moduleName(), 'ArbitratorModule'); + } + /** + * @notice Test that the decodeRequestData function returns the correct values + */ + + function test_decodeRequestData(address _arbitrator) public { + // Mock data + bytes memory _requestData = abi.encode(address(_arbitrator)); + + // Test: decode the given request data + IArbitratorModule.RequestParameters memory _requestParameters = arbitratorModule.decodeRequestData(_requestData); + + // Check: decoded values match original values? + assertEq(_requestParameters.arbitrator, _arbitrator); + } + + /** + * @notice Test that the status is correctly retrieved + */ + function test_getStatus(uint256 _status, bytes32 _disputeId) public { + _status = bound(_status, 0, uint256(IArbitratorModule.ArbitrationStatus.Resolved)); + IArbitratorModule.ArbitrationStatus _arbitratorStatus = IArbitratorModule.ArbitrationStatus(_status); + + // Store the mock dispute + arbitratorModule.forTest_setDisputeStatus(_disputeId, _arbitratorStatus); + + // Check: The correct status is returned? + assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(_status)); + } +} + +contract ArbitratorModule_Unit_StartResolution is BaseTest { + /** + * @notice Test that the escalate function works as expected + */ + function test_startResolution(bytes32 _disputeId, IOracle.Request calldata _request) public { + // Mock and expect the dummy dispute + mockDispute.requestId = _getId(_request); + + // Store the requestData + bytes memory _requestData = abi.encode(address(arbitrator)); + + // Mock and expect the callback to the arbitrator + _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); + + vm.prank(address(oracle)); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + + // Check: is status now Escalated? + assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Active)); + } + + function test_emitsEvent(bytes32 _disputeId, IOracle.Request calldata _request) public { + // Mock and expect the dummy dispute + mockDispute.requestId = _getId(_request); + + // Store the requestData + bytes memory _requestData = abi.encode(address(arbitrator)); + + // Mock and expect the callback to the arbitrator + _mockAndExpect(address(arbitrator), abi.encodeCall(arbitrator.resolve, (_disputeId)), abi.encode(bytes(''))); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(arbitratorModule)); + emit ResolutionStarted(mockDispute.requestId, _disputeId); + + vm.prank(address(oracle)); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + } + + function test_revertInvalidCaller(address _caller, bytes32 _disputeId, IOracle.Request calldata _request) public { + vm.assume(_caller != address(oracle)); + + // Check: does it revert if the caller is not the Oracle? + vm.expectRevert(abi.encodeWithSelector(IModule.Module_OnlyOracle.selector)); + + vm.prank(_caller); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + } + + function test_revertIfEmptyArbitrator(bytes32 _disputeId, IOracle.Request calldata _request) public { + // Mock and expect the dummy dispute + mockDispute.requestId = _getId(_request); + + // Store the requestData + bytes memory _requestData = abi.encode(address(0)); + + // Check: revert? + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidArbitrator.selector)); + + // Test: escalate the dispute + vm.prank(address(oracle)); + arbitratorModule.startResolution(_disputeId, _request, mockResponse, mockDispute); + } +} + +contract ArbitratorModule_Unit_ResolveDispute is BaseTest { + /** + * @notice Test that the resolve function works as expected + */ + function test_resolveDispute(bytes32 _disputeId, uint256 _status, IOracle.Request calldata _request) public { + vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); + vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); + IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect( + address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + ); + + // Mock and expect IOracle.updateDisputeStatus to be called + _mockAndExpect( + address(oracle), + abi.encodeCall(oracle.updateDisputeStatus, (_request, mockResponse, mockDispute, _arbitratorStatus)), + abi.encode() + ); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + + // Check: is status now Resolved? + assertEq(uint256(arbitratorModule.getStatus(_disputeId)), uint256(IArbitratorModule.ArbitrationStatus.Resolved)); + } + + function test_revertsIfInvalidResolveStatus( + bytes32 _disputeId, + uint256 _status, + IOracle.Request calldata _request + ) public { + vm.assume(_status <= uint256(IOracle.DisputeStatus.Escalated)); + IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect( + address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + ); + + // Check: does it revert if the resolution status is invalid? + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidResolutionStatus.selector)); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } + + function test_emitsEvent(bytes32 _disputeId, uint256 _status, IOracle.Request calldata _request) public { + vm.assume(_status <= uint256(IOracle.DisputeStatus.Lost)); + vm.assume(_status > uint256(IOracle.DisputeStatus.Escalated)); + IOracle.DisputeStatus _arbitratorStatus = IOracle.DisputeStatus(_status); + + // Mock and expect getAnswer to be called on the arbitrator + _mockAndExpect( + address(arbitrator), abi.encodeCall(arbitrator.getAnswer, (_disputeId)), abi.encode(_arbitratorStatus) + ); + + // Mock and expect IOracle.updateDisputeStatus to be called + _mockAndExpect( + address(oracle), + abi.encodeCall(oracle.updateDisputeStatus, (_request, mockResponse, mockDispute, _arbitratorStatus)), + abi.encode() + ); + + // Check: is the event emitted? + vm.expectEmit(true, true, true, true, address(arbitratorModule)); + emit DisputeResolved(_getId(_request), _disputeId, _arbitratorStatus); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } + + /** + * @notice resolve dispute reverts if the dispute status isn't Active + */ + function test_revertIfInvalidDispute(IOracle.Request calldata _request, bytes32 _disputeId) public { + // Test the 3 different invalid status (None, Won, Lost) + for (uint256 _status; _status < uint256(type(IOracle.DisputeStatus).max); _status++) { + if (IOracle.DisputeStatus(_status) == IOracle.DisputeStatus.Escalated) continue; + // Create a new dummy dispute + IOracle.Dispute memory _dispute = IOracle.Dispute({ + disputer: makeAddr('disputer'), + proposer: makeAddr('proposer'), + responseId: _getId(mockResponse), + requestId: _getId(_request) + }); + + // Check: does it revert if the dispute id is invalid? + vm.expectRevert(abi.encodeWithSelector(IArbitratorModule.ArbitratorModule_InvalidDisputeId.selector)); + + vm.prank(address(oracle)); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } + } + + /** + * @notice Test that the resolve function reverts if the caller isn't the arbitrator + */ + function test_revertIfWrongSender(bytes32 _disputeId, address _caller, IOracle.Request calldata _request) public { + vm.assume(_caller != address(oracle)); + + // Check: does it revert if not called by the Oracle? + vm.expectRevert(IModule.Module_OnlyOracle.selector); + + vm.prank(_caller); + arbitratorModule.resolveDispute(_disputeId, _request, mockResponse, mockDispute); + } +}