Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: bonded response release unutilized response #71

Merged
merged 9 commits into from
Nov 13, 2024
Prev Previous commit
Next Next commit
test: integration tests for releasing unutilized response
  • Loading branch information
gas1cent committed Nov 13, 2024
commit edc81f1bb6cc6fccd8a5cdae2e1eaa9e100b52e5
54 changes: 0 additions & 54 deletions solidity/test/integration/Finalization.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -156,60 +156,6 @@ contract Integration_Finalization is IntegrationBase {
assertEq(_finalizedResponseId, bytes32(0));
}

/**
* @notice Release unutilized response bond after finalization.
*/
function test_releaseUnutilizedResponse(bytes calldata _calldata) public {
// Create request, response and dispute it
bytes32 _requestId = _createRequest();
mockResponse.requestId = _requestId;
bytes32 _responseId = _proposeResponse();
mockDispute.requestId = _requestId;
mockDispute.responseId = _responseId;
bytes32 _disputeId = _disputeResponse();

mockResponse.requestId = bytes32(0);
IOracle.Response memory _emptyResponse = mockResponse;
mockResponse.requestId = _requestId;

// After the deadline has passed, finalize with an empty response
vm.warp(block.timestamp + _expectedDeadline);
vm.prank(_finalizer);
oracle.finalize(mockRequest, _emptyResponse, _createAccessControl());

// Check: is request finalized with an empty response?
assertEq(oracle.finalizedResponseId(_requestId), bytes32(0));
assertEq(oracle.finalizedAt(_requestId), block.timestamp);

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _expectedBondSize);

// Escalate dispute
vm.prank(disputer);
oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl());

vm.mockCall(
address(_mockArbitrator),
abi.encodeCall(IArbitrator.getAnswer, (_disputeId)),
abi.encode(IOracle.DisputeStatus.Lost)
);

// Second step: resolving the dispute
vm.prank(disputer);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute, _createAccessControl());

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _expectedBondSize);

// Now the proposer should be able to release their unused response
vm.prank(proposer);
_responseModule.releaseUnutilizedResponse(mockRequest, mockResponse);

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), 0);
assertEq(_accountingExtension.balanceOf(proposer, usdc), _expectedBondSize * 2);
}

/**
* @notice Updates the finalization module and its data.
*/
Expand Down
169 changes: 169 additions & 0 deletions solidity/test/integration/ReleaseUnutilizedResponse.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import './IntegrationBase.sol';

contract Integration_ReleaseUnutilizedResponse is IntegrationBase {
address internal _finalizer = makeAddr('finalizer');
bytes32 _requestId;

Check warning on line 8 in solidity/test/integration/ReleaseUnutilizedResponse.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (16.x)

Explicitly mark visibility of state
bytes32 _responseId;

Check warning on line 9 in solidity/test/integration/ReleaseUnutilizedResponse.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (16.x)

Explicitly mark visibility of state
bytes32 _disputeId;

Check warning on line 10 in solidity/test/integration/ReleaseUnutilizedResponse.t.sol

View workflow job for this annotation

GitHub Actions / Run Linters (16.x)

Explicitly mark visibility of state

function setUp() public override {
super.setUp();

_deposit(_accountingExtension, requester, usdc, _expectedReward);
_deposit(_accountingExtension, proposer, usdc, _expectedBondSize);
_deposit(_accountingExtension, disputer, usdc, _expectedBondSize);

// Create request, response and dispute it
_requestId = _createRequest();
mockResponse.requestId = _requestId;

_responseId = _proposeResponse(mockResponse);
mockDispute.requestId = _requestId;
mockDispute.responseId = _responseId;

_disputeId = _disputeResponse();
}

/**
* @notice Tests the release of unutilized response when the request is finalized with an empty response
*/
function test_releaseUnutilizedResponse_withoutResponse() public {
mockResponse.requestId = bytes32(0);
IOracle.Response memory _emptyResponse = mockResponse;
mockResponse.requestId = _requestId;

// Finalizing the request with an empty response
_finalizeRequest(_emptyResponse);

// Check: is request finalized with an empty response?
assertEq(oracle.finalizedResponseId(_requestId), bytes32(0));
assertEq(oracle.finalizedAt(_requestId), block.timestamp);

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _expectedBondSize, 'Balance check 1');

// The dispute is escalated and has no resolution
_escalateAndResolveDispute(IOracle.DisputeStatus.NoResolution);

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _expectedBondSize, 'Balance check 2');

// Now the proposer should be able to release their unused response
vm.prank(proposer);
_responseModule.releaseUnutilizedResponse(mockRequest, mockResponse);

// Check: proposers funds are not bonded anymore?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), 0, 'Balance check 3');

// Check: proposer received the disputer's bond?
assertEq(_accountingExtension.balanceOf(proposer, usdc), _expectedBondSize * 2);
}

/**
* @notice Tests the release of unutilized response when the request is finalized with a correct response
*/
function test_releaseUnutilizedResponse_withResponse() public {
IOracle.Response memory _unutilizedResponse = mockResponse;

IOracle.Response memory _acceptedResponse = mockResponse;
_acceptedResponse.response = bytes('123');
_deposit(_accountingExtension, proposer, usdc, _expectedBondSize);
_proposeResponse(_acceptedResponse);

// Check: impossible to release responses before the request is finalized
vm.expectRevert(IBondedResponseModule.BondedResponseModule_InvalidReleaseParameters.selector);
_responseModule.releaseUnutilizedResponse(mockRequest, _unutilizedResponse);

// Finalizing the request with a correct response
_finalizeRequest(_acceptedResponse);

// Check: impossible to release utilized responses
vm.expectRevert(IBondedResponseModule.BondedResponseModule_InvalidReleaseParameters.selector);
_responseModule.releaseUnutilizedResponse(mockRequest, _acceptedResponse);

// Check: is request finalized with an empty response?
assertEq(oracle.finalizedResponseId(_requestId), _getId(_acceptedResponse));
assertEq(oracle.finalizedAt(_requestId), block.timestamp);

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _expectedBondSize, 'Balance check 1');

// The dispute is escalated and has no resolution
_escalateAndResolveDispute(IOracle.DisputeStatus.Lost);

// Check: proposers funds are still bonded?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), _expectedBondSize, 'Balance check 2');

// Now the proposer should be able to release their unused response
vm.prank(proposer);
_responseModule.releaseUnutilizedResponse(mockRequest, _unutilizedResponse);

// Check: proposers funds are not bonded anymore?
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), 0, 'Balance check 3');

// Check: proposer received
// - their own bonds for 2 responses
// - disputer's bond for winning the dispute
// - the reward for the correct response
assertEq(_accountingExtension.balanceOf(proposer, usdc), _expectedBondSize * 3 + _expectedReward);
}

/**
* @notice Tests reverts that happen when trying to release a response that is being or that has been successfully disputed
*/
function test_releaseUnutilizedResponse_withDisputedResponse() public {
mockResponse.requestId = bytes32(0);
IOracle.Response memory _emptyResponse = mockResponse;
mockResponse.requestId = _requestId;

// Finalizing the request with an empty response
_finalizeRequest(_emptyResponse);

// Check: is request finalized with an empty response?
assertEq(oracle.finalizedResponseId(_requestId), bytes32(0));
assertEq(oracle.finalizedAt(_requestId), block.timestamp);

// Check: impossible to release the disputed response while the dispute is active
vm.expectRevert(IBondedResponseModule.BondedResponseModule_InvalidReleaseParameters.selector);
_responseModule.releaseUnutilizedResponse(mockRequest, mockResponse);

// The dispute is escalated and has no resolution
_escalateAndResolveDispute(IOracle.DisputeStatus.Won);

// Check: impossible to release the disputed response if the disputer won
vm.expectRevert(IBondedResponseModule.BondedResponseModule_InvalidReleaseParameters.selector);
_responseModule.releaseUnutilizedResponse(mockRequest, mockResponse);

// Check: proposers bond is slashed
assertEq(_accountingExtension.bondedAmountOf(proposer, usdc, _requestId), 0);
}

function _escalateAndResolveDispute(IOracle.DisputeStatus _status) internal {
// Escalate dispute
vm.prank(disputer);
oracle.escalateDispute(mockRequest, mockResponse, mockDispute, _createAccessControl());

// Resolve with the provided status
vm.mockCall(address(_mockArbitrator), abi.encodeCall(IArbitrator.getAnswer, (_disputeId)), abi.encode(_status));

vm.prank(disputer);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute, _createAccessControl());
}

function _finalizeRequest(IOracle.Response memory _response) internal {
// After the deadline has passed, finalize with the given response
vm.warp(block.timestamp + _expectedDeadline);
vm.prank(_finalizer);
oracle.finalize(mockRequest, _response, _createAccessControl());
}

function _proposeResponse(IOracle.Response memory _response) internal returns (bytes32 _responseId) {
vm.startPrank(proposer);
_accountingExtension.approveModule(address(_responseModule));
_responseId = oracle.proposeResponse(mockRequest, _response, _createAccessControl());
vm.stopPrank();
}
}
Loading