Skip to content

Commit

Permalink
fix: release unutilized response once (#88)
Browse files Browse the repository at this point in the history
* fix: release unutilized response once

* fix: inheritdoc

* test: add integration
  • Loading branch information
0xShaito authored Dec 9, 2024
1 parent 02906ce commit af33b14
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 0 deletions.
7 changes: 7 additions & 0 deletions solidity/contracts/modules/response/BondedResponseModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {IOracle} from '@defi-wonderland/prophet-core/solidity/interfaces/IOracle
import {IBondedResponseModule} from '../../../interfaces/modules/response/IBondedResponseModule.sol';

contract BondedResponseModule is Module, IBondedResponseModule {
/// @inheritdoc IBondedResponseModule
mapping(bytes32 _responseId => bool _released) public responseReleased;

constructor(IOracle _oracle) Module(_oracle) {}

/// @inheritdoc IModule
Expand Down Expand Up @@ -102,6 +105,8 @@ contract BondedResponseModule is Module, IBondedResponseModule {
/// @inheritdoc IBondedResponseModule
function releaseUnutilizedResponse(IOracle.Request calldata _request, IOracle.Response calldata _response) external {
bytes32 _responseId = _validateResponse(_request, _response);
if (responseReleased[_responseId]) revert BondedResponseModule_ResponseAlreadyReleased();

bytes32 _disputeId = ORACLE.disputeOf(_responseId);

if (_disputeId > 0) {
Expand All @@ -116,6 +121,8 @@ contract BondedResponseModule is Module, IBondedResponseModule {
revert BondedResponseModule_InvalidReleaseParameters();
}

responseReleased[_responseId] = true;

RequestParameters memory _params = decodeRequestData(_request.responseModuleData);
_params.accountingExtension.release({
_bonder: _response.proposer,
Expand Down
15 changes: 15 additions & 0 deletions solidity/interfaces/modules/response/IBondedResponseModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ interface IBondedResponseModule is IResponseModule {
*/
error BondedResponseModule_InvalidReleaseParameters();

/**
* @notice Thrown when trying to release an already released response
*/
error BondedResponseModule_ResponseAlreadyReleased();

/*///////////////////////////////////////////////////////////////
STRUCTS
//////////////////////////////////////////////////////////////*/
Expand All @@ -76,6 +81,16 @@ interface IBondedResponseModule is IResponseModule {
uint256 disputeWindow;
}

/*///////////////////////////////////////////////////////////////
VARIABLES
//////////////////////////////////////////////////////////////*/
/**
* @notice Returns true if the response was already released because it was unutilized
* @param _responseId The response id to check
* @return _released true if the response was already released
*/
function responseReleased(bytes32 _responseId) external view returns (bool _released);

/*///////////////////////////////////////////////////////////////
LOGIC
//////////////////////////////////////////////////////////////*/
Expand Down
9 changes: 9 additions & 0 deletions solidity/test/integration/ReleaseUnutilizedResponse.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ contract Integration_ReleaseUnutilizedResponse is IntegrationBase {

// Check: proposer received their bond back?
assertEq(_accountingExtension.balanceOf(proposer, usdc), _expectedBondSize);

// The response is marked as released
assertTrue(_responseModule.responseReleased(_getId(mockResponse)));

// Trying to release again reverts
vm.expectRevert(IBondedResponseModule.BondedResponseModule_ResponseAlreadyReleased.selector);

vm.prank(proposer);
_responseModule.releaseUnutilizedResponse(mockRequest, mockResponse);
}

/**
Expand Down
59 changes: 59 additions & 0 deletions solidity/test/unit/modules/response/BondedResponseModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,65 @@ contract BondedResponseModule_Unit_ReleaseUnutilizedResponse is BaseTest {
bondedResponseModule.releaseUnutilizedResponse(mockRequest, mockResponse);
}

/**
* @notice Finalized request, undisputed response, the bond should be released
*/
function test_releaseUnutilizedResponse_revertsIfCalledTwice(
IERC20 _token,
uint256 _bondSize,
uint256 _deadline,
bytes32 _finalizedResponseId,
uint256 _finalizedAt
) public {
// Setting the response module data
mockRequest.responseModuleData = abi.encode(accounting, _token, _bondSize, _deadline, _baseDisputeWindow);

// Updating IDs
bytes32 _requestId = _getId(mockRequest);
mockResponse.requestId = _requestId;
mockResponse.proposer = proposer;
bytes32 _responseId = _getId(mockResponse);

// Can't claim back the bond of the response that was finalized
vm.assume(_finalizedResponseId > 0);
vm.assume(_finalizedResponseId != _responseId);
vm.assume(_finalizedAt > 0);

// Mock and expect IOracle.disputeOf to be called
_mockAndExpect(address(oracle), abi.encodeCall(IOracle.disputeOf, (_responseId)), abi.encode(bytes32(0)));

// Mock and expect IOracle.finalizedResponseId to be called
_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.finalizedResponseId, (_requestId)), abi.encode(_finalizedResponseId)
);

_mockAndExpect(address(oracle), abi.encodeCall(IOracle.finalizedAt, (_requestId)), abi.encode(_finalizedAt));

_mockAndExpect(
address(oracle), abi.encodeCall(IOracle.responseCreatedAt, (_responseId)), abi.encode(block.timestamp)
);

// Mock and expect IAccountingExtension.release to be called
_mockAndExpect(
address(accounting),
abi.encodeCall(IAccountingExtension.release, (proposer, _getId(mockRequest), _token, _bondSize)),
abi.encode(true)
);

// Before releasing the response should be marked as not released
vm.assertFalse(bondedResponseModule.responseReleased(_getId(mockResponse)));

// Release the response bond
bondedResponseModule.releaseUnutilizedResponse(mockRequest, mockResponse);

// Saves locally that the response was already released
vm.assertTrue(bondedResponseModule.responseReleased(_getId(mockResponse)));

// Should revert if we call it again
vm.expectRevert(IBondedResponseModule.BondedResponseModule_ResponseAlreadyReleased.selector);
bondedResponseModule.releaseUnutilizedResponse(mockRequest, mockResponse);
}

/**
* @notice Non-finalized request, undisputed response, the call should revert
*/
Expand Down

0 comments on commit af33b14

Please sign in to comment.