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: release unutilized response once #88

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -5,9 +5,9 @@

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();
Expand Down Expand Up @@ -60,6 +60,15 @@

// 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
Loading