diff --git a/package.json b/package.json index 2e2e4ff..3a19116 100644 --- a/package.json +++ b/package.json @@ -56,5 +56,6 @@ "solhint-plugin-defi-wonderland": "1.1.2", "sort-package-json": "2.4.1", "standard-version": "9.5.0" - } + }, + "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/solidity/contracts/modules/dispute/BondEscalationModule.sol b/solidity/contracts/modules/dispute/BondEscalationModule.sol index 1920064..090a07d 100644 --- a/solidity/contracts/modules/dispute/BondEscalationModule.sol +++ b/solidity/contracts/modules/dispute/BondEscalationModule.sol @@ -60,9 +60,6 @@ contract BondEscalationModule is AccessControllerModule, IBondEscalationModule { // Only the first dispute of a request should go through the bond escalation // Consecutive disputes should be handled by the resolution module if (_escalation.status == BondEscalationStatus.None) { - if (block.timestamp > ORACLE.disputeCreatedAt(_disputeId) + _params.bondEscalationDeadline) { - revert BondEscalationModule_BondEscalationOver(); - } _escalation.status = BondEscalationStatus.Active; _escalation.disputeId = _disputeId; emit BondEscalationStatusUpdated(_dispute.requestId, _disputeId, BondEscalationStatus.Active); @@ -174,8 +171,8 @@ contract BondEscalationModule is AccessControllerModule, IBondEscalationModule { // Refund the disputer, the bond escalation status stays Escalated _newStatus = BondEscalationStatus.Escalated; _params.accountingExtension.release({ - _requestId: _dispute.requestId, _bonder: _dispute.disputer, + _requestId: _dispute.requestId, _token: _params.bondToken, _amount: _params.bondSize }); diff --git a/solidity/contracts/modules/response/BondedResponseModule.sol b/solidity/contracts/modules/response/BondedResponseModule.sol index 7d973a0..c552c1a 100644 --- a/solidity/contracts/modules/response/BondedResponseModule.sol +++ b/solidity/contracts/modules/response/BondedResponseModule.sol @@ -76,9 +76,10 @@ contract BondedResponseModule is AccessControllerModule, IBondedResponseModule { RequestParameters memory _params = decodeRequestData(_request.responseModuleData); // review: _finalizer is Oracle:finalize's accessControl.user - bool _isModule = ORACLE.allowedModule(_response.requestId, _finalizer); + bytes32 _requestId = _getId(_request); + bool _isModule = ORACLE.allowedModule(_requestId, _finalizer); - if (!_isModule && block.timestamp < ORACLE.requestCreatedAt(_response.requestId) + _params.deadline) { + if (!_isModule && block.timestamp < ORACLE.requestCreatedAt(_requestId) + _params.deadline) { revert BondedResponseModule_TooEarlyToFinalize(); } @@ -90,13 +91,13 @@ contract BondedResponseModule is AccessControllerModule, IBondedResponseModule { _params.accountingExtension.release({ _bonder: _response.proposer, - _requestId: _response.requestId, + _requestId: _requestId, _token: _params.bondToken, _amount: _params.bondSize }); } - emit RequestFinalized(_response.requestId, _response, _finalizer); + emit RequestFinalized(_requestId, _response, _finalizer); } /// @inheritdoc IBondedResponseModule diff --git a/solidity/test/integration/ContractCallRequest.t.sol b/solidity/test/integration/ContractCallRequest.t.sol index 4247994..8c917c0 100644 --- a/solidity/test/integration/ContractCallRequest.t.sol +++ b/solidity/test/integration/ContractCallRequest.t.sol @@ -50,8 +50,9 @@ contract Integration_ContractCallRequest is IntegrationBase { uint256 _requestCreatedAt = oracle.requestCreatedAt(_requestId); // mock an empty response - mockResponse = - IOracle.Response({proposer: makeAddr('not-the-proposer'), requestId: bytes32(0), response: bytes('')}); + mockResponse = IOracle.Response({proposer: address(0), requestId: bytes32(0), response: bytes('')}); + + assertEq(oracle.responseCreatedAt(_getId(mockResponse)), 0); // expect call to accounting to release requester's funds vm.expectCall( @@ -59,7 +60,7 @@ contract Integration_ContractCallRequest is IntegrationBase { abi.encodeCall(IAccountingExtension.release, (mockRequest.requester, _requestId, usdc, _expectedReward)) ); - // assert response does not exist + // assert the empty response does not exist assertEq(oracle.responseCreatedAt(_getId(mockResponse)), 0); vm.warp(_requestCreatedAt + _expectedDeadline); diff --git a/solidity/test/integration/EscalateDispute.t.sol b/solidity/test/integration/EscalateDispute.t.sol index 967463c..b96f071 100644 --- a/solidity/test/integration/EscalateDispute.t.sol +++ b/solidity/test/integration/EscalateDispute.t.sol @@ -6,6 +6,13 @@ import './IntegrationBase.sol'; contract Integration_EscalateDispute is IntegrationBase { bytes32 internal _requestId; bytes32 internal _disputeId; + uint256 internal _pledgeSize = _expectedBondSize; + uint256 internal _tyingBuffer = 1 days; + uint256 internal _expectedResponseDeadline = _expectedDeadline * 2; + uint256 internal _disputeCreatedAt; + + address internal _escalator = makeAddr('escalator'); + address internal _resolver = makeAddr('resolver'); function setUp() public override { super.setUp(); @@ -29,7 +36,7 @@ contract Integration_EscalateDispute is IntegrationBase { accountingExtension: _bondEscalationAccounting, bondToken: usdc, bondSize: _expectedBondSize, - deadline: _expectedDeadline, + deadline: _expectedResponseDeadline, disputeWindow: _baseDisputeWindow }) ); @@ -39,22 +46,22 @@ contract Integration_EscalateDispute is IntegrationBase { accountingExtension: _bondEscalationAccounting, bondToken: usdc, bondSize: _expectedBondSize, - maxNumberOfEscalations: 1, + maxNumberOfEscalations: 2, bondEscalationDeadline: _expectedDeadline, - tyingBuffer: 0, + tyingBuffer: _tyingBuffer, disputeWindow: 0 }) ); mockRequest.disputeModule = address(_bondEscalationModule); + _resetMockIds(); + vm.startPrank(requester); _bondEscalationAccounting.approveModule(address(_requestModule)); _requestId = oracle.createRequest(mockRequest, _ipfsHash, _createAccessControl()); vm.stopPrank(); - _resetMockIds(); - // Propose a response and dispute it _deposit(_bondEscalationAccounting, proposer, usdc, _expectedBondSize); vm.startPrank(proposer); @@ -67,18 +74,263 @@ contract Integration_EscalateDispute is IntegrationBase { _bondEscalationAccounting.approveModule(address(_bondEscalationModule)); _disputeId = oracle.disputeResponse(mockRequest, mockResponse, mockDispute, _createAccessControl(disputer)); vm.stopPrank(); + + _disputeCreatedAt = oracle.disputeCreatedAt(_disputeId); + } + + function test_disputeWonDispute() public { + mockDispute.requestId = _requestId; + + // Bond escalation should call pledge + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IBondEscalationAccounting.pledge, (disputer, mockRequest, mockDispute, usdc, _pledgeSize)) + ); + + // Pledge for dispute + _deposit(_bondEscalationAccounting, disputer, usdc, _pledgeSize * 3); + vm.startPrank(disputer); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Pledge revert if can be only surpassed by 1 + vm.expectRevert(IBondEscalationModule.BondEscalationModule_CanOnlySurpassByOnePledge.selector); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + vm.stopPrank(); + + // Bond escalation should call pledge + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IBondEscalationAccounting.pledge, (proposer, mockRequest, mockDispute, usdc, _pledgeSize)) + ); + + // Pledge for dispute + _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize); + vm.prank(proposer); + _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute, _createAccessControl()); + + // Get the bond escalation + IBondEscalationModule.BondEscalation memory _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + + // Check that the pledge was registered + assertEq(_bondEscalationModule.pledgesAgainstDispute(_requestId, proposer), 1); + assertEq(_bondEscalation.amountOfPledgesAgainstDispute, 1); + + vm.startPrank(disputer); + + // Pledge revert if break tie during tying buffer + vm.warp(_disputeCreatedAt + _expectedDeadline + 1); + vm.expectRevert(IBondEscalationModule.BondEscalationModule_CannotBreakTieDuringTyingBuffer.selector); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Pledege revert if bond escalation is over + vm.warp(_disputeCreatedAt + _expectedDeadline + _tyingBuffer + 1); + vm.expectRevert(IBondEscalationModule.BondEscalationModule_BondEscalationOver.selector); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Roll back the timestamp because we need to simulate the custom error "break tie during tying buffer" and "bond escalation over" + vm.warp(_disputeCreatedAt); + + // Pledge second time for dispute + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Pledge revert if the maximum number of escalations is reached + vm.expectRevert(IBondEscalationModule.BondEscalationModule_MaxNumberOfEscalationsReached.selector); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Get the bond escalation + _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + + // Check that the pledge was registered + uint256 _pledgesForDispute = _bondEscalationModule.pledgesForDispute(_requestId, disputer); + assertEq(_pledgesForDispute, 2); + assertEq(_bondEscalation.amountOfPledgesForDispute, 2); + + // Calculate the amount to pay + uint256 _amountToPay = _pledgeSize + (_pledgeSize / 2); + + // Settle bond escalation reverts if bond escalation is not over + vm.expectRevert(IBondEscalationModule.BondEscalationModule_BondEscalationNotOver.selector); + _bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute); + + // Warp to pass the escalation deadline + vm.warp(_disputeCreatedAt + _expectedDeadline + _tyingBuffer + 1); + + // The bond escalation accounting should have been called to settle the bond escalation + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall( + IBondEscalationAccounting.onSettleBondEscalation, + (mockRequest, mockDispute, usdc, _amountToPay, _pledgesForDispute) + ) + ); + + // The bond escalation accounting should have been called to pay the proposer + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IAccountingExtension.pay, (_requestId, proposer, disputer, usdc, _pledgeSize)) + ); + + // The bond escalation accounting should have been called to release the proposer's bond + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IAccountingExtension.release, (disputer, _requestId, usdc, _pledgeSize)) + ); + + // Escalate dispute should won the dispute + _bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute); + + //The oracle should have been called to finalize the dispute + assertTrue(IOracle.DisputeStatus.Won == oracle.disputeStatus(_disputeId)); + + // //The new bond escalation should have the status DisputerWon + _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + assertTrue(_bondEscalation.status == IBondEscalationModule.BondEscalationStatus.DisputerWon); + } + + function test_disputeLostDispute() public { + mockDispute.requestId = _requestId; + + // Bond escalation should call pledge + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IBondEscalationAccounting.pledge, (proposer, mockRequest, mockDispute, usdc, _pledgeSize)) + ); + + // Pledge for dispute + _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize * 3); + vm.startPrank(proposer); + _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute, _createAccessControl()); + + // Pledge revert if can be only surpassed by 1 + vm.expectRevert(IBondEscalationModule.BondEscalationModule_CanOnlySurpassByOnePledge.selector); + _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute, _createAccessControl()); + vm.stopPrank(); + + // Bond escalation should call pledge + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IBondEscalationAccounting.pledge, (disputer, mockRequest, mockDispute, usdc, _pledgeSize)) + ); + + // Pledge for dispute + _deposit(_bondEscalationAccounting, disputer, usdc, _pledgeSize); + vm.prank(disputer); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Get the bond escalation + IBondEscalationModule.BondEscalation memory _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + + // Check that the pledge was registered + assertEq(_bondEscalationModule.pledgesForDispute(_requestId, disputer), 1); + assertEq(_bondEscalation.amountOfPledgesForDispute, 1); + + // Pledge revert if the maximum number of escalations is reached + vm.startPrank(proposer); + _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute, _createAccessControl()); + vm.expectRevert(IBondEscalationModule.BondEscalationModule_MaxNumberOfEscalationsReached.selector); + _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute, _createAccessControl()); + + // Get the bond escalation + _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + + // Check that the pledge was registered + uint256 _pledgesAgainstDispute = _bondEscalationModule.pledgesAgainstDispute(_requestId, proposer); + assertEq(_pledgesAgainstDispute, 2); + assertEq(_bondEscalation.amountOfPledgesAgainstDispute, 2); + + // Calculate the amount to pay + uint256 _amountToPay = _pledgeSize + (_pledgeSize / 2); + + // Settle bond escalation reverts if bond escalation is not over + vm.expectRevert(IBondEscalationModule.BondEscalationModule_BondEscalationNotOver.selector); + _bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute); + + // Warp to pass the escalation deadline + vm.warp(_disputeCreatedAt + _expectedResponseDeadline + 1); + + // The bond escalation accounting should have been called to settle the bond escalation + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall( + IBondEscalationAccounting.onSettleBondEscalation, + (mockRequest, mockDispute, usdc, _amountToPay, _pledgesAgainstDispute) + ) + ); + + // The bond escalation accounting should have been called to pay the proposer + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IAccountingExtension.pay, (_requestId, disputer, proposer, usdc, _pledgeSize)) + ); + + // The bond escalation accounting should not have been called to release the proposer's bond + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall(IAccountingExtension.release, (disputer, _requestId, usdc, _pledgeSize)), + 0 + ); + + // Escalate dispute should won the dispute + _bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute); + + //The oracle should have been called to finalize the dispute + assertTrue(IOracle.DisputeStatus.Lost == oracle.disputeStatus(_disputeId)); + + // //The new bond escalation should have the status DisputerLost + _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + assertTrue(_bondEscalation.status == IBondEscalationModule.BondEscalationStatus.DisputerLost); } - function test_escalateDispute() public { - address _escalator = makeAddr('escalator'); + function test_escalateDisputeArbitratorResolveNoResolution() public { // Escalate dispute reverts if dispute does not exist mockDispute.requestId = bytes32(0); vm.expectRevert(ValidatorLib.ValidatorLib_InvalidDisputeBody.selector); vm.prank(_escalator); - oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl(_escalator)); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); mockDispute.requestId = _requestId; + // Escalate dispute reverts if escalation is not over + vm.expectRevert(IBondEscalationModule.BondEscalationModule_BondEscalationNotOver.selector); + vm.prank(_escalator); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // Check that the dispute is active + assertTrue( + _bondEscalationModule.getEscalation(_requestId).status == IBondEscalationModule.BondEscalationStatus.Active + ); + + // Pledge for dispute + _deposit(_bondEscalationAccounting, disputer, usdc, _pledgeSize); + vm.prank(disputer); + _bondEscalationModule.pledgeForDispute(mockRequest, mockDispute, _createAccessControl()); + + // Warp blocks to pass the escalation deadline + vm.warp(_disputeCreatedAt + _expectedResponseDeadline + 1); + + // Escalate dispute reverts if dispute is not escalatable + vm.expectRevert(IBondEscalationModule.BondEscalationModule_NotEscalatable.selector); + vm.prank(_escalator); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // Roll back the timestamp because we need to simulate the custom error "not escalatable" + vm.warp(_disputeCreatedAt); + + // Pledge against dispute + _deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize); + vm.prank(proposer); + _bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute, _createAccessControl()); + + // Warp blocks to pass the escalation deadline + vm.warp(_disputeCreatedAt + _expectedDeadline + _tyingBuffer + 1); + + // Settle bond escalation reverts if dispute is not escalated + vm.expectRevert(IBondEscalationModule.BondEscalationModule_ShouldBeEscalated.selector); + _bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute); + + // Create bond escalation + IBondEscalationModule.BondEscalation memory _bondEscalation; + // The oracle should call the dispute module vm.expectCall( address(_bondEscalationModule), @@ -101,16 +353,15 @@ contract Integration_EscalateDispute is IntegrationBase { ); // We escalate the dispute - vm.warp(block.timestamp + _expectedDeadline + 1); vm.prank(_escalator); - oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl(_escalator)); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); // We check that the dispute was escalated IOracle.DisputeStatus _disputeStatus = oracle.disputeStatus(_disputeId); assertTrue(_disputeStatus == IOracle.DisputeStatus.Escalated); // The BondEscalationModule should now have the escalation status escalated - IBondEscalationModule.BondEscalation memory _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + _bondEscalation = _bondEscalationModule.getEscalation(_requestId); assertTrue(_bondEscalation.status == IBondEscalationModule.BondEscalationStatus.Escalated); // The ArbitratorModule should have updated the status of the dispute @@ -119,6 +370,210 @@ contract Integration_EscalateDispute is IntegrationBase { // Escalate dispute reverts if dispute is not active vm.expectRevert(abi.encodeWithSelector(IOracle.Oracle_CannotEscalate.selector, _disputeId)); vm.prank(_escalator); - oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl(_escalator)); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // Revert if bond escalation cant be settled + vm.expectRevert(IBondEscalationModule.BondEscalationModule_BondEscalationCantBeSettled.selector); + _bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute); + + // The bond escalation accounting should have been called to release the proposer's bond + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall( + IAccountingExtension.release, (mockDispute.disputer, mockDispute.requestId, usdc, _expectedBondSize) + ) + ); + + // Resolve the dispute escalated with no resolution + _mockArbitrator.setAnswer(IOracle.DisputeStatus.NoResolution); + vm.prank(_resolver); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // The arbitrator module should have updated the status of the dispute + assertTrue(_arbitratorModule.getStatus(_disputeId) == IArbitratorModule.ArbitrationStatus.Resolved); + + // The BondEscalationModule should have updated the status of the escalation + assertTrue( + _bondEscalationModule.getEscalation(_requestId).status == IBondEscalationModule.BondEscalationStatus.Escalated + ); + + // Oracle should have updated the status of the dispute + assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.NoResolution); + + // Propose a new response and dispute it + _deposit(_bondEscalationAccounting, proposer, usdc, _expectedBondSize); + mockResponse.response = bytes('new response'); + vm.prank(proposer); + oracle.proposeResponse(mockRequest, mockResponse, _createAccessControl()); + + // Get the new response id + mockDispute.responseId = _getId(mockResponse); + + // The oracle should call the dispute module with the new dispute id + bytes32 _newDisputeId = _getId(mockDispute); + + // The oracle should call the dispute module + vm.expectCall( + address(oracle), + abi.encodeCall( + IOracle.escalateDispute, + (mockRequest, mockResponse, mockDispute, _createAccessControl(address(_bondEscalationModule))) + ) + ); + + vm.expectCall( + address(_bondEscalationModule), + abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_newDisputeId, mockRequest, mockResponse, mockDispute)) + ); + + _deposit(_bondEscalationAccounting, disputer, usdc, _expectedBondSize); + vm.prank(disputer); + oracle.disputeResponse(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // We check that the dispute was escalated + _disputeStatus = oracle.disputeStatus(_newDisputeId); + assertTrue(_disputeStatus == IOracle.DisputeStatus.Escalated); + + // The BondEscalationModule should now have the escalation status escalated + _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + assertTrue(_bondEscalation.status == IBondEscalationModule.BondEscalationStatus.Escalated); + } + + function test_escalateDisputeArbitratorResolveLost() public { + mockDispute.requestId = _requestId; + + // The oracle should call the dispute module + vm.expectCall( + address(_bondEscalationModule), + abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)) + ); + + // The oracle should call startResolution in the resolution module + vm.expectCall( + address(_arbitratorModule), + abi.encodeCall(IResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)) + ); + + // The arbitrator module should call the arbitrator + vm.expectCall( + address(_mockArbitrator), + abi.encodeCall( + MockArbitrator.resolve, + (mockRequest, mockResponse, mockDispute, _createAccessControl(address(_arbitratorModule))) + ) + ); + + // We escalate the dispute + vm.warp(_disputeCreatedAt + _expectedDeadline + 1); + vm.prank(_escalator); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // We check that the dispute was escalated + IOracle.DisputeStatus _disputeStatus = oracle.disputeStatus(_disputeId); + assertTrue(_disputeStatus == IOracle.DisputeStatus.Escalated); + + // The BondEscalationModule should now have the escalation status escalated + IBondEscalationModule.BondEscalation memory _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + assertTrue(_bondEscalation.status == IBondEscalationModule.BondEscalationStatus.Escalated); + + // The ArbitratorModule should have updated the status of the dispute + assertTrue(_arbitratorModule.getStatus(_disputeId) == IArbitratorModule.ArbitrationStatus.Active); + + // The bond escalation accounting should have been called to pay the proposer + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall( + IAccountingExtension.pay, (_requestId, mockDispute.disputer, mockResponse.proposer, usdc, _expectedBondSize) + ) + ); + + // Resolve the dispute escalated with no resolution + _mockArbitrator.setAnswer(IOracle.DisputeStatus.Lost); + vm.prank(_resolver); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // The arbitrator module should have updated the status of the dispute + assertTrue(_arbitratorModule.getStatus(_disputeId) == IArbitratorModule.ArbitrationStatus.Resolved); + + // The BondEscalationModule should have updated the status of the escalation + assertTrue( + _bondEscalationModule.getEscalation(_requestId).status == IBondEscalationModule.BondEscalationStatus.DisputerLost + ); + + // Oracle should have updated the status of the dispute + assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.Lost); + } + + function test_escalateDisputeArbitratorResolveWon() public { + mockDispute.requestId = _requestId; + + // The oracle should call the dispute module + vm.expectCall( + address(_bondEscalationModule), + abi.encodeCall(IDisputeModule.onDisputeStatusChange, (_disputeId, mockRequest, mockResponse, mockDispute)) + ); + + // The oracle should call startResolution in the resolution module + vm.expectCall( + address(_arbitratorModule), + abi.encodeCall(IResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)) + ); + + // The arbitrator module should call the arbitrator + vm.expectCall( + address(_mockArbitrator), + abi.encodeCall( + MockArbitrator.resolve, + (mockRequest, mockResponse, mockDispute, _createAccessControl(address(_arbitratorModule))) + ) + ); + + // We escalate the dispute + vm.warp(_disputeCreatedAt + _expectedDeadline + 1); + vm.prank(_escalator); + oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // We check that the dispute was escalated + IOracle.DisputeStatus _disputeStatus = oracle.disputeStatus(_disputeId); + assertTrue(_disputeStatus == IOracle.DisputeStatus.Escalated); + + // The BondEscalationModule should now have the escalation status escalated + IBondEscalationModule.BondEscalation memory _bondEscalation = _bondEscalationModule.getEscalation(_requestId); + assertTrue(_bondEscalation.status == IBondEscalationModule.BondEscalationStatus.Escalated); + + // The ArbitratorModule should have updated the status of the dispute + assertTrue(_arbitratorModule.getStatus(_disputeId) == IArbitratorModule.ArbitrationStatus.Active); + + // The bond escalation accounting should have been called to pay the disputer + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall( + IAccountingExtension.pay, (_requestId, mockResponse.proposer, mockDispute.disputer, usdc, _expectedBondSize) + ) + ); + + // The bond escalation accounting should have been called to release the proposer's bond + vm.expectCall( + address(_bondEscalationAccounting), + abi.encodeCall( + IAccountingExtension.release, (mockDispute.disputer, mockDispute.requestId, usdc, _expectedBondSize) + ) + ); + + // Resolve the dispute escalated with no resolution + _mockArbitrator.setAnswer(IOracle.DisputeStatus.Won); + vm.prank(_resolver); + oracle.resolveDispute(mockRequest, mockResponse, mockDispute, _createAccessControl()); + + // The arbitrator module should have updated the status of the dispute + assertTrue(_arbitratorModule.getStatus(_disputeId) == IArbitratorModule.ArbitrationStatus.Resolved); + + // The BondEscalationModule should have updated the status of the escalation + assertTrue( + _bondEscalationModule.getEscalation(_requestId).status == IBondEscalationModule.BondEscalationStatus.DisputerWon + ); + + // Oracle should have updated the status of the dispute + assertTrue(oracle.disputeStatus(_disputeId) == IOracle.DisputeStatus.Won); } } diff --git a/solidity/test/integration/IntegrationBase.sol b/solidity/test/integration/IntegrationBase.sol index 87c8f63..cb86d3d 100644 --- a/solidity/test/integration/IntegrationBase.sol +++ b/solidity/test/integration/IntegrationBase.sol @@ -191,6 +191,9 @@ contract IntegrationBase is DSTestPlus, TestConstants, Helpers { mockRequest.nonce = uint96(oracle.totalRequestCount()); _resetMockIds(); + + // Simulate the time passing + vm.warp(1 days); } function _mineBlock() internal { diff --git a/solidity/test/unit/modules/dispute/BondEscalationModule.t.sol b/solidity/test/unit/modules/dispute/BondEscalationModule.t.sol index af4ea0a..8927bfa 100644 --- a/solidity/test/unit/modules/dispute/BondEscalationModule.t.sol +++ b/solidity/test/unit/modules/dispute/BondEscalationModule.t.sol @@ -471,55 +471,6 @@ contract BondEscalationModule_Unit_DisputeResponse is BaseTest { bondEscalationModule.disputeResponse(mockRequest, mockResponse, mockDispute); } - /** - * @notice Tests that disputeResponse succeeds if someone dispute after the bond escalation deadline is over - */ - function test_succeedIfDisputeAfterBondingEscalationDeadline( - uint256 _timestamp, - IBondEscalationModule.RequestParameters memory _params - ) public assumeFuzzable(address(_params.accountingExtension)) { - _timestamp = bound(_timestamp, block.timestamp, type(uint128).max); - - // update mock timestamp - responseCreatedAt = _timestamp; - - // Set deadline to timestamp so we are still in the bond escalation period - _params.bondEscalationDeadline = 3 days; - _params.disputeWindow = 5 days; - mockRequest.disputeModuleData = abi.encode(_params); - - bytes32 _requestId = _getId(mockRequest); - mockResponse.requestId = _requestId; - - bytes32 _responseId = _getId(mockResponse); - mockDispute.responseId = _responseId; - mockDispute.requestId = _requestId; - - // Mock and expect IOracle.responseCreatedAt to be called - _mockAndExpect( - address(oracle), abi.encodeCall(IOracle.responseCreatedAt, (_responseId)), abi.encode(responseCreatedAt) - ); - _mockAndExpect( - address(oracle), abi.encodeCall(IOracle.disputeCreatedAt, (_getId(mockDispute))), abi.encode(_timestamp) - ); - - // Mock and expect the accounting extension to be called - _mockAndExpect( - address(_params.accountingExtension), - abi.encodeWithSignature( - 'bond(address,bytes32,address,uint256)', mockDispute.disputer, _requestId, _params.bondToken, _params.bondSize - ), - abi.encode(true) - ); - - vm.warp(_timestamp + _params.bondEscalationDeadline + 1); - - // Check: does it revert if the bond escalation is over? - vm.expectRevert(IBondEscalationModule.BondEscalationModule_BondEscalationOver.selector); - vm.prank(address(oracle)); - bondEscalationModule.disputeResponse(mockRequest, mockResponse, mockDispute); - } - /** * @notice Tests that disputeResponse succeeds in starting the bond escalation mechanism when someone disputes * the first propose before the bond escalation deadline is over. @@ -558,10 +509,6 @@ contract BondEscalationModule_Unit_DisputeResponse is BaseTest { address(oracle), abi.encodeCall(IOracle.responseCreatedAt, (_responseId)), abi.encode(responseCreatedAt) ); - _mockAndExpect( - address(oracle), abi.encodeCall(IOracle.disputeCreatedAt, (_disputeId)), abi.encode(disputeCreatedAt) - ); - vm.expectEmit(true, true, true, true, address(bondEscalationModule)); emit ResponseDisputed({ _requestId: _requestId, @@ -612,11 +559,6 @@ contract BondEscalationModule_Unit_DisputeResponse is BaseTest { address(oracle), abi.encodeCall(IOracle.responseCreatedAt, (_responseId)), abi.encode(responseCreatedAt) ); - // Mock and expect IOracle.responseCreatedAt to be called - _mockAndExpect( - address(oracle), abi.encodeCall(IOracle.disputeCreatedAt, (_disputeId)), abi.encode(disputeCreatedAt) - ); - // Mock and expect the accounting extension to be called _mockAndExpect( address(_params.accountingExtension), diff --git a/solidity/test/unit/modules/response/BondedResponseModule.t.sol b/solidity/test/unit/modules/response/BondedResponseModule.t.sol index eedb4bd..c553bc8 100644 --- a/solidity/test/unit/modules/response/BondedResponseModule.t.sol +++ b/solidity/test/unit/modules/response/BondedResponseModule.t.sol @@ -464,6 +464,47 @@ contract BondedResponseModule_Unit_FinalizeRequest is BaseTest { vm.prank(address(oracle)); bondedResponseModule.finalizeRequest(mockRequest, mockResponse, _finalizer); } + + function test_finalizeWithoutResponse( + IERC20 _token, + uint256 _bondSize, + uint256 _disputeWindow, + address _proposer, + uint256 _deadline + ) public { + _disputeWindow = bound(_disputeWindow, 61, 365 days); + + // Check correct calls are made if deadline has passed + _deadline = bound(_deadline, 1, type(uint248).max); + mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _disputeWindow); + bytes32 _requestId = _getId(mockRequest); + mockResponse.requestId = _requestId; + mockResponse.proposer = _proposer; + + // Mock and expect IOracle.allowedModule to be called + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.allowedModule, (_requestId, address(this))), abi.encode(false) + ); + + _mockAndExpect( + address(oracle), abi.encodeCall(IOracle.requestCreatedAt, (_getId(mockRequest))), abi.encode(requestCreatedAt) + ); + + // Empty response + mockResponse = IOracle.Response({proposer: address(0), requestId: bytes32(0), response: bytes('')}); + + // Response does not exist + _mockAndExpect(address(oracle), abi.encodeCall(IOracle.responseCreatedAt, (_getId(mockResponse))), abi.encode(0)); + + // Check: is event emitted? + vm.expectEmit(true, true, true, true, address(bondedResponseModule)); + emit RequestFinalized({_requestId: _getId(mockRequest), _response: mockResponse, _finalizer: address(this)}); + + vm.warp(requestCreatedAt + _deadline + _disputeWindow); + + vm.prank(address(oracle)); + bondedResponseModule.finalizeRequest(mockRequest, mockResponse, address(this)); + } } contract BondedResponseModule_Unit_ReleaseUnutilizedResponse is BaseTest {