From df5b7b943b16a5efa29cab07a911c66899c2ee2e Mon Sep 17 00:00:00 2001 From: zorzal Date: Tue, 10 Dec 2024 14:44:38 -0500 Subject: [PATCH] fix: restrict duplicate calls to `resolveDispute` --- solidity/contracts/Oracle.sol | 8 ++++++++ solidity/interfaces/IOracle.sol | 13 +++++++++++++ solidity/test/unit/Oracle.t.sol | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/solidity/contracts/Oracle.sol b/solidity/contracts/Oracle.sol index 9283cb9..5e0b1e1 100644 --- a/solidity/contracts/Oracle.sol +++ b/solidity/contracts/Oracle.sol @@ -34,6 +34,9 @@ contract Oracle is IOracle, OracleAccessController { /// @inheritdoc IOracle mapping(bytes32 _disputeId => DisputeStatus _status) public disputeStatus; + /// @inheritdoc IOracle + mapping(bytes32 _disputeId => bool _resolved) public disputeResolved; + /// @inheritdoc IOracle mapping(uint256 _requestNumber => bytes32 _id) public nonceToRequestId; @@ -278,6 +281,10 @@ contract Oracle is IOracle, OracleAccessController { revert Oracle_InvalidDisputeId(_disputeId); } + if (disputeResolved[_disputeId]) { + revert Oracle_DisputeAlreadyResolved(); + } + // Revert if the dispute is not active nor escalated DisputeStatus _currentStatus = disputeStatus[_disputeId]; if (_currentStatus != DisputeStatus.Active && _currentStatus != DisputeStatus.Escalated) { @@ -288,6 +295,7 @@ contract Oracle is IOracle, OracleAccessController { revert Oracle_NoResolutionModule(_disputeId); } + disputeResolved[_disputeId] = true; IResolutionModule(_request.resolutionModule).resolveDispute(_disputeId, _request, _response, _dispute); emit DisputeResolved(_disputeId, _dispute); diff --git a/solidity/interfaces/IOracle.sol b/solidity/interfaces/IOracle.sol index 6246de7..74eb39d 100644 --- a/solidity/interfaces/IOracle.sol +++ b/solidity/interfaces/IOracle.sol @@ -168,6 +168,11 @@ interface IOracle is IOracleAccessController { */ error Oracle_InvalidDisputer(); + /** + * @notice Thrown when the dispute is already resolved + */ + error Oracle_DisputeAlreadyResolved(); + /*/////////////////////////////////////////////////////////////// ENUMS //////////////////////////////////////////////////////////////*/ @@ -299,6 +304,14 @@ interface IOracle is IOracleAccessController { */ function disputeStatus(bytes32 _disputeId) external view returns (DisputeStatus _status); + /** + * @notice Returns whether the dispute has been resolved + * + * @param _disputeId The id of the dispute + * @return _resolved True if the dispute has been resolved, false otherwise + */ + function disputeResolved(bytes32 _disputeId) external view returns (bool _resolved); + /** * @notice The id of each request in chronological order * diff --git a/solidity/test/unit/Oracle.t.sol b/solidity/test/unit/Oracle.t.sol index 6034398..66d1fa2 100644 --- a/solidity/test/unit/Oracle.t.sol +++ b/solidity/test/unit/Oracle.t.sol @@ -50,6 +50,10 @@ contract MockOracle is Oracle { disputeStatus[_disputeId] = _status; } + function mock_setDisputeResolved(bytes32 _disputeId, bool _resolved) external { + disputeResolved[_disputeId] = _resolved; + } + function mock_setRequestId(uint256 _nonce, bytes32 _requestId) external { nonceToRequestId[_nonce] = _requestId; } @@ -877,6 +881,23 @@ contract Oracle_Unit_ResolveDispute is BaseTest { oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); } + function test_resolveDispute_revertsWhenAlreadyResolved() 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_setDisputeResolved(_disputeId, true); + + // Check: reverts? + vm.expectRevert(IOracle.Oracle_DisputeAlreadyResolved.selector); + + // Test: resolve the dispute + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, mockAccessControl); + } + /** * @notice Test the revert when the function is called with an non-existent dispute id */