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

feat: handle NoResolution case in the BondEscalationModule #40

Merged
merged 7 commits into from
Dec 18, 2023
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"package.json": "sort-package-json"
},
"dependencies": {
"@defi-wonderland/prophet-core-contracts": "0.0.0-6ac0f50d",
"@defi-wonderland/prophet-core-contracts": "0.0.0-f88b32e2",
"@openzeppelin/contracts": "4.9.5",
"solmate": "https://github.com/transmissions11/solmate.git#bfc9c25865a274a7827fea5abf6e4fb64fc64e6c"
},
Expand Down
16 changes: 10 additions & 6 deletions solidity/contracts/extensions/BondEscalationAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
function onSettleBondEscalation(
bytes32 _requestId,
bytes32 _disputeId,
bool _forVotesWon,
IERC20 _token,
uint256 _amountPerPledger,
uint256 _winningPledgersLength
Expand All @@ -59,7 +58,6 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount

escalationResults[_disputeId] = EscalationResult({
requestId: _requestId,
forVotesWon: _forVotesWon,
token: _token,
amountPerPledger: _amountPerPledger,
bondEscalationModule: IBondEscalationModule(msg.sender)
Expand All @@ -68,7 +66,6 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
emit BondEscalationSettled({
_requestId: _requestId,
_disputeId: _disputeId,
_forVotesWon: _forVotesWon,
_token: _token,
_amountPerPledger: _amountPerPledger,
_winningPledgersLength: _winningPledgersLength
Expand All @@ -82,10 +79,17 @@ contract BondEscalationAccounting is AccountingExtension, IBondEscalationAccount
bytes32 _requestId = _result.requestId;
if (pledgerClaimed[_requestId][_pledger]) revert BondEscalationAccounting_AlreadyClaimed();

IOracle.DisputeStatus _status = ORACLE.disputeStatus(_disputeId);
uint256 _amountPerPledger = _result.amountPerPledger;
uint256 _numberOfPledges = _result.forVotesWon
? _result.bondEscalationModule.pledgesForDispute(_requestId, _pledger)
: _result.bondEscalationModule.pledgesAgainstDispute(_requestId, _pledger);
uint256 _numberOfPledges;

if (_status == IOracle.DisputeStatus.NoResolution) {
_numberOfPledges = 1;
} else {
_numberOfPledges = _status == IOracle.DisputeStatus.Won
? _result.bondEscalationModule.pledgesForDispute(_requestId, _pledger)
: _result.bondEscalationModule.pledgesAgainstDispute(_requestId, _pledger);
}

IERC20 _token = _result.token;
uint256 _claimAmount = _amountPerPledger * _numberOfPledges;
Expand Down
154 changes: 97 additions & 57 deletions solidity/contracts/modules/dispute/BondEscalationModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,15 @@ contract BondEscalationModule is Module, IBondEscalationModule {
IOracle.Dispute calldata _dispute
) external onlyOracle {
RequestParameters memory _params = decodeRequestData(_request.disputeModuleData);

BondEscalation storage _escalation = _escalations[_dispute.requestId];
IOracle.DisputeStatus _disputeStatus = ORACLE.disputeStatus(_disputeId);
BondEscalationStatus _newStatus;

if (_disputeStatus == IOracle.DisputeStatus.Escalated) {
if (_disputeId == _escalation.disputeId) {
if (_disputeId == _escalation.disputeId) {
// The bond escalated (first) dispute has been updated
if (_disputeStatus == IOracle.DisputeStatus.Escalated) {
// The dispute has been escalated to the Resolution module
// Make sure the bond escalation deadline has passed and update the status
if (block.timestamp <= _params.bondEscalationDeadline) revert BondEscalationModule_BondEscalationNotOver();

if (
Expand All @@ -92,77 +95,114 @@ contract BondEscalationModule is Module, IBondEscalationModule {
revert BondEscalationModule_NotEscalatable();
}

_escalation.status = BondEscalationStatus.Escalated;
emit BondEscalationStatusUpdated(_dispute.requestId, _disputeId, BondEscalationStatus.Escalated);
return;
} else {
emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: IOracle.DisputeStatus.Escalated});
return;
}
}

bool _won = _disputeStatus == IOracle.DisputeStatus.Won;
_newStatus = BondEscalationStatus.Escalated;
} else if (_disputeStatus == IOracle.DisputeStatus.NoResolution) {
// The resolution module failed to reach a resolution
// Refund the disputer and all pledgers, the bond escalation escalation status stays Escalated
_newStatus = BondEscalationStatus.Escalated;

if (_escalation.amountOfPledgesForDispute + _escalation.amountOfPledgesAgainstDispute > 0) {
_params.accountingExtension.onSettleBondEscalation({
_requestId: _dispute.requestId,
_disputeId: _disputeId,
_token: _params.bondToken,
_amountPerPledger: _params.bondSize,
_winningPledgersLength: _escalation.amountOfPledgesForDispute + _escalation.amountOfPledgesAgainstDispute
});
}

_params.accountingExtension.pay({
_requestId: _dispute.requestId,
_payer: _won ? _dispute.proposer : _dispute.disputer,
_receiver: _won ? _dispute.disputer : _dispute.proposer,
_token: _params.bondToken,
_amount: _params.bondSize
});
_params.accountingExtension.release({
_requestId: _dispute.requestId,
_bonder: _dispute.disputer,
_token: _params.bondToken,
_amount: _params.bondSize
});
} else {
// One of the sides won
// Pay the winner (proposer/disputer) and the pledgers, the bond escalation status changes to DisputerWon/DisputerLost
bool _won = _disputeStatus == IOracle.DisputeStatus.Won;
_newStatus = _won ? BondEscalationStatus.DisputerWon : BondEscalationStatus.DisputerLost;

if (_won) {
_params.accountingExtension.release({
_requestId: _dispute.requestId,
_bonder: _dispute.disputer,
_token: _params.bondToken,
_amount: _params.bondSize
});
}
uint256 _pledgesForDispute = _escalation.amountOfPledgesForDispute;
uint256 _pledgesAgainstDispute = _escalation.amountOfPledgesAgainstDispute;

if (_disputeId == _escalation.disputeId) {
// The dispute has been escalated to the Resolution module
if (_escalation.status == BondEscalationStatus.Escalated) {
if (_escalation.amountOfPledgesAgainstDispute == 0) {
return;
if (_pledgesAgainstDispute > 0) {
uint256 _amountToPay = _won
? _params.bondSize
+ FixedPointMathLib.mulDivDown(_pledgesAgainstDispute, _params.bondSize, _pledgesForDispute)
: _params.bondSize
+ FixedPointMathLib.mulDivDown(_pledgesForDispute, _params.bondSize, _pledgesAgainstDispute);

_params.accountingExtension.onSettleBondEscalation({
_requestId: _dispute.requestId,
_disputeId: _escalation.disputeId,
_token: _params.bondToken,
_amountPerPledger: _amountToPay,
_winningPledgersLength: _won ? _pledgesForDispute : _pledgesAgainstDispute
});
}

BondEscalationStatus _newStatus = _won ? BondEscalationStatus.DisputerWon : BondEscalationStatus.DisputerLost;
_escalation.status = _newStatus;

emit BondEscalationStatusUpdated(_dispute.requestId, _disputeId, _newStatus);
_params.accountingExtension.pay({
_requestId: _dispute.requestId,
_payer: _won ? _dispute.proposer : _dispute.disputer,
_receiver: _won ? _dispute.disputer : _dispute.proposer,
_token: _params.bondToken,
_amount: _params.bondSize
});

_params.accountingExtension.onSettleBondEscalation({
if (_won) {
_params.accountingExtension.release({
_requestId: _dispute.requestId,
_bonder: _dispute.disputer,
_token: _params.bondToken,
_amount: _params.bondSize
});
}
}
} else {
// The non-bond escalated (second and subsequent) dispute has been updated
if (_disputeStatus == IOracle.DisputeStatus.Escalated) {
// The dispute has been escalated to the Resolution module
// Update the bond escalation status to Escalated
_newStatus = BondEscalationStatus.Escalated;
} else if (_disputeStatus == IOracle.DisputeStatus.NoResolution) {
// The resolution module failed to reach a resolution
// Refund the disputer, the bond escalation status stays Escalated
_newStatus = BondEscalationStatus.Escalated;
_params.accountingExtension.release({
_requestId: _dispute.requestId,
_disputeId: _disputeId,
_forVotesWon: _won,
_bonder: _dispute.disputer,
_token: _params.bondToken,
_amountPerPledger: _params.bondSize << 1,
_winningPledgersLength: _won ? _escalation.amountOfPledgesForDispute : _escalation.amountOfPledgesAgainstDispute
_amount: _params.bondSize
});
} else {
// The status has changed to Won or Lost
uint256 _pledgesForDispute = _escalation.amountOfPledgesForDispute;
uint256 _pledgesAgainstDispute = _escalation.amountOfPledgesAgainstDispute;
bool _disputersWon = _pledgesForDispute > _pledgesAgainstDispute;
// One of the sides won
// Pay the winner (proposer/disputer), the bond escalation status changes to DisputerWon/DisputerLost
bool _won = _disputeStatus == IOracle.DisputeStatus.Won;
_newStatus = _won ? BondEscalationStatus.DisputerWon : BondEscalationStatus.DisputerLost;

uint256 _amountToPay = _disputersWon
? _params.bondSize + FixedPointMathLib.mulDivDown(_pledgesAgainstDispute, _params.bondSize, _pledgesForDispute)
: _params.bondSize + FixedPointMathLib.mulDivDown(_pledgesForDispute, _params.bondSize, _pledgesAgainstDispute);

_params.accountingExtension.onSettleBondEscalation({
_params.accountingExtension.pay({
_requestId: _dispute.requestId,
_disputeId: _escalation.disputeId,
_forVotesWon: _disputersWon,
_payer: _won ? _dispute.proposer : _dispute.disputer,
_receiver: _won ? _dispute.disputer : _dispute.proposer,
_token: _params.bondToken,
_amountPerPledger: _amountToPay,
_winningPledgersLength: _disputersWon ? _pledgesForDispute : _pledgesAgainstDispute
_amount: _params.bondSize
});

if (_won) {
_params.accountingExtension.release({
_requestId: _dispute.requestId,
_bonder: _dispute.disputer,
_token: _params.bondToken,
_amount: _params.bondSize
});
}
}
}

IOracle.DisputeStatus _status = ORACLE.disputeStatus(_disputeId);
emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: _status});
_escalation.status = _newStatus;
emit BondEscalationStatusUpdated(_dispute.requestId, _disputeId, _newStatus);
emit DisputeStatusChanged({_disputeId: _disputeId, _dispute: _dispute, _status: _disputeStatus});
}

////////////////////////////////////////////////////////////////////
Expand Down
21 changes: 2 additions & 19 deletions solidity/interfaces/extensions/IBondEscalationAccounting.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,12 @@ interface IBondEscalationAccounting is IAccountingExtension {
*
* @param _requestId The ID of the bond-escalated request
* @param _disputeId The ID of the bond-escalated dispute
* @param _forVotesWon True if the winning side were the for votes
* @param _token The address of the token being paid out
* @param _amountPerPledger The amount of `_token` to be paid for each winning pledgers
* @param _winningPledgersLength The number of winning pledgers
*/
event BondEscalationSettled(
bytes32 _requestId,
bytes32 _disputeId,
bool _forVotesWon,
IERC20 _token,
uint256 _amountPerPledger,
uint256 _winningPledgersLength
bytes32 _requestId, bytes32 _disputeId, IERC20 _token, uint256 _amountPerPledger, uint256 _winningPledgersLength
);

/**
Expand Down Expand Up @@ -120,14 +114,12 @@ interface IBondEscalationAccounting is IAccountingExtension {
/**
* @notice Contains the data of the result of an escalation. Is used by users to claim their pledges
* @param requestId The ID of the bond-escalated request
* @param forVotesWon Whether the for votes won the dispute
* @param token The address of the token being paid out
* @param amountPerPledger The amount of token paid to each of the winning pledgers
* @param bondEscalationModule The address of the bond escalation module that was used
*/
struct EscalationResult {
bytes32 requestId;
bool forVotesWon;
IERC20 token;
uint256 amountPerPledger;
IBondEscalationModule bondEscalationModule;
Expand All @@ -151,20 +143,13 @@ interface IBondEscalationAccounting is IAccountingExtension {
*
* @param _disputeId The ID of the bond-escalated dispute
* @return _requestId The ID of the bond-escalated request
* @return _forVotesWon True if the for votes won the dispute
* @return _token Address of the token being paid as a reward for winning the bond escalation
* @return _amountPerPledger Amount of `_token` to be rewarded to each of the winning pledgers
* @return _bondEscalationModule The address of the bond escalation module that was used
*/
function escalationResults(bytes32 _disputeId)
external
returns (
bytes32 _requestId,
bool _forVotesWon,
IERC20 _token,
uint256 _amountPerPledger,
IBondEscalationModule _bondEscalationModule
);
returns (bytes32 _requestId, IERC20 _token, uint256 _amountPerPledger, IBondEscalationModule _bondEscalationModule);

/**
* @notice True if the given pledger has claimed their reward for the given dispute
Expand Down Expand Up @@ -198,15 +183,13 @@ interface IBondEscalationAccounting is IAccountingExtension {
*
* @param _requestId The ID of the bond-escalated request
* @param _disputeId The ID of the bond-escalated dispute
* @param _forVotesWon True if the for votes won the dispute
* @param _token Address of the token being paid as a reward for winning the bond escalation
* @param _amountPerPledger Amount of `_token` to be rewarded to each of the winning pledgers
* @param _winningPledgersLength Amount of pledges that won the dispute
*/
function onSettleBondEscalation(
bytes32 _requestId,
bytes32 _disputeId,
bool _forVotesWon,
IERC20 _token,
uint256 _amountPerPledger,
uint256 _winningPledgersLength
Expand Down
14 changes: 7 additions & 7 deletions solidity/test/integration/BondEscalation.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -101,24 +101,24 @@ contract Integration_BondEscalation is IntegrationBase {
}

function test_proposerWins() public {
// // Step 1: Proposer pledges against the dispute
// Step 1: Proposer pledges against the dispute
_deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize);
vm.prank(proposer);
_bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute);

// // Step 2: Disputer doubles down
// Step 2: Disputer doubles down
_deposit(_bondEscalationAccounting, disputer, usdc, _pledgeSize);
vm.prank(disputer);
_bondEscalationModule.pledgeForDispute(mockRequest, mockDispute);

// // Step 3: Proposer doubles down
// Step 3: Proposer doubles down
_deposit(_bondEscalationAccounting, proposer, usdc, _pledgeSize);
vm.prank(proposer);
_bondEscalationModule.pledgeAgainstDispute(mockRequest, mockDispute);

// // Step 4: Disputer runs out of capital
// // Step 5: External parties see that Disputer's dispute was wrong so they don't join to escalate
// // Step 6: Proposer response's is deemed correct and final once the bond escalation window is over
// Step 4: Disputer runs out of capital
// Step 5: External parties see that Disputer's dispute was wrong so they don't join to escalate
// Step 6: Proposer response's is deemed correct and final once the bond escalation window is over
vm.warp(_expectedDeadline + _tyingBuffer + 1);
_bondEscalationModule.settleBondEscalation(mockRequest, mockResponse, mockDispute);

Expand Down Expand Up @@ -191,7 +191,7 @@ contract Integration_BondEscalation is IntegrationBase {
_bondEscalationAccounting.claimEscalationReward(_disputeId, requester);
assertEq(_bondEscalationAccounting.balanceOf(requester, usdc), 0, 'Mismatch: Requester balance');

// Test: The proposer has lost his pledge
// // Test: The proposer has lost his pledge
_bondEscalationAccounting.claimEscalationReward(_disputeId, proposer);
assertEq(_bondEscalationAccounting.balanceOf(proposer, usdc), 0, 'Mismatch: Proposer balance');

Expand Down
Loading