Skip to content

Commit

Permalink
test: add more test cases
Browse files Browse the repository at this point in the history
I found two bugs in ERC20Resolution:
- `claimVote` allows voters to withdraw their casted votes any number of
  any number of times. A voter could cast a single vote into a
resolution and call `claimVote` until all votes from a resolution are
withdrawn.
- when you try to resolve a not escalated dispute it reverts with
  `ERC20ResolutionModule_AlreadyResolved`. This one also happens in
PrivateERC20Resolution.
  • Loading branch information
xorsal committed Oct 16, 2024
1 parent dce0b57 commit d544ba0
Show file tree
Hide file tree
Showing 3 changed files with 338 additions and 22 deletions.
1 change: 1 addition & 0 deletions solidity/test/integration/IntegrationBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pragma solidity ^0.8.19;
import {console} from 'forge-std/console.sol';

import {IOracle, Oracle} from '@defi-wonderland/prophet-core/solidity/contracts/Oracle.sol';
import {IValidator} from '@defi-wonderland/prophet-core/solidity/interfaces/IValidator.sol';
import {IDisputeModule} from '@defi-wonderland/prophet-core/solidity/interfaces/modules/dispute/IDisputeModule.sol';
import {IFinalityModule} from '@defi-wonderland/prophet-core/solidity/interfaces/modules/finality/IFinalityModule.sol';
import {IRequestModule} from '@defi-wonderland/prophet-core/solidity/interfaces/modules/request/IRequestModule.sol';
Expand Down
214 changes: 206 additions & 8 deletions solidity/test/integration/PrivaterResolution.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,28 +44,59 @@ contract Integration_PrivateResolution is IntegrationBase {
_deposit(_accountingExtension, proposer, usdc, _expectedBondSize);
_deposit(_accountingExtension, disputer, usdc, _expectedBondSize);

_setupEscalatedDispute();
_setupDispute();
}

function test_resolve_noVotes() public {
// expect call to startResolution
vm.expectCall(
address(privateERC20ResolutionModule),
abi.encodeCall(
IPrivateERC20ResolutionModule.startResolution, (_disputeId, mockRequest, mockResponse, mockDispute)
)
);

oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

// expect call to update dispute' status as lost
vm.expectCall(
address(oracle),
abi.encodeCall(IOracle.updateDisputeStatus, (mockRequest, mockResponse, mockDispute, IOracle.DisputeStatus.Lost))
);

// expect call to resolveDispute
vm.expectCall(
address(privateERC20ResolutionModule),
abi.encodeCall(IPrivateERC20ResolutionModule.resolveDispute, (_disputeId, mockRequest, mockResponse, mockDispute))
);

(uint256 _startTime, uint256 _totalVotes) = privateERC20ResolutionModule.escalations(_disputeId);
assertEq(_startTime, block.timestamp);
assertEq(_totalVotes, 0);

vm.warp(block.timestamp + _committingTimeWindow + _revealingTimeWindow + 1);
// expect revert when try to resolve before the committing phase is over
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_OnGoingCommittingPhase.selector);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

// warp past the committing phase
vm.warp(block.timestamp + _committingTimeWindow + 1);
// expect revert when try to resolve before the revealing phase is over
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_OnGoingRevealingPhase.selector);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

// warp past the revealing phase
vm.warp(block.timestamp + _revealingTimeWindow);

// successfully resolve the dispute
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

(_startTime,) = privateERC20ResolutionModule.escalations(_disputeId);
(_startTime, _totalVotes) = privateERC20ResolutionModule.escalations(_disputeId);
assertEq(_totalVotes, 0);
}

function test_resolve_enoughVotes() public {
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

// we have enough votes to reach the quorum
uint256 _votes = _minimumQuorum + 1;

Expand All @@ -85,7 +116,7 @@ contract Integration_PrivateResolution is IntegrationBase {
vm.expectCall(address(_votingToken), abi.encodeCall(IERC20.transfer, (_voterA, _votes)));

// warp into the commiting window
vm.warp(block.timestamp);
vm.warp(block.timestamp + 1);

deal(address(_votingToken), _voterA, _votes);
vm.startPrank(_voterA);
Expand All @@ -95,16 +126,28 @@ contract Integration_PrivateResolution is IntegrationBase {

// warp past the commiting window
vm.warp(block.timestamp + _committingTimeWindow + 1);

// assert has enough voting tokens
assertEq(_votingToken.balanceOf(_voterA), _votes);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);
// assert voting tokens were transfered
assertEq(_votingToken.balanceOf(_voterA), 0);
assertEq(_votingToken.balanceOf(address(privateERC20ResolutionModule)), _votes);

vm.stopPrank();

// warp past the revealing window
vm.warp(block.timestamp + _revealingTimeWindow);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

// assert voting tokens were transfered back to the voter.
assertEq(_votingToken.balanceOf(_voterA), _votes);
assertEq(_votingToken.balanceOf(address(privateERC20ResolutionModule)), 0);
}

function test_resolve_notEnoughVotes() public {
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

uint256 _votes = _minimumQuorum - 1;

vm.expectCall(
Expand All @@ -120,7 +163,7 @@ contract Integration_PrivateResolution is IntegrationBase {
vm.expectCall(address(_votingToken), abi.encodeCall(IERC20.transfer, (_voterA, _votes)));

// warp into the commiting window
vm.warp(block.timestamp);
vm.warp(block.timestamp + 1);

deal(address(_votingToken), _voterA, _votes);

Expand All @@ -130,22 +173,177 @@ contract Integration_PrivateResolution is IntegrationBase {
bytes32 _commitment = privateERC20ResolutionModule.computeCommitment(_disputeId, _votes, _goodSalt);
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, _commitment);

// warp into the committing phase
vm.warp(block.timestamp + _committingTimeWindow);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

vm.stopPrank();

vm.warp(block.timestamp + _revealingTimeWindow);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);
}

function test_resolve_noEscalation() public {
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_AlreadyResolved.selector);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);
}

function test_zeroVotes() public {
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

uint256 _votes = 0;

// expect call to transfer `0` tokens to voterB
vm.expectCall(address(_votingToken), abi.encodeCall(IERC20.transfer, (_voterA, 0)));

vm.startPrank(_voterA);
bytes32 _commitment = privateERC20ResolutionModule.computeCommitment(_disputeId, _votes, _goodSalt);
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, _commitment);
vm.stopPrank();

// warp past the commiting phase
vm.warp(block.timestamp + _committingTimeWindow + 1);

// expert to revert because the sender is not correct
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_WrongRevealData.selector);
vm.prank(_voterB);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

vm.prank(_voterA);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

vm.warp(block.timestamp + _revealingTimeWindow + 1);
oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

assertEq(_votingToken.balanceOf(_voterA), 0);
assertEq(_votingToken.balanceOf(address(privateERC20ResolutionModule)), 0);
}

function test_commit() public {
uint256 _votes = 0;

vm.startPrank(_voterA);
bytes32 _commitment = privateERC20ResolutionModule.computeCommitment(_disputeId, _votes, _goodSalt);

// expect revert when trying to commit a vote into an already resolved dispute
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_AlreadyResolved.selector);
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, _commitment);

oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

// expect revert when trying to commit a vote with an empty commitment
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_EmptyCommitment.selector);
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, bytes32(''));

// successfully commit a vote
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, _commitment);

// warp past the committing phase
vm.warp(block.timestamp + _committingTimeWindow);

// expect revert when trying to commit after the committing phase is over
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_CommittingPhaseOver.selector);
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, _commitment);

vm.stopPrank();
}

function test_reveal() public {
uint256 _votes = 0;

vm.startPrank(_voterA);

// expect revert when reveal a commit into a not escalated dispute
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_DisputeNotEscalated.selector);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

bytes32 _commitment = privateERC20ResolutionModule.computeCommitment(_disputeId, _votes, _goodSalt);

// escalate and commit a vote using `_goodSalt`
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);
privateERC20ResolutionModule.commitVote(mockRequest, mockDispute, _commitment);

// expect revert when trying to reveal vote during committing phase
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_OnGoingCommittingPhase.selector);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

// warp past the committing phase
vm.warp(block.timestamp + _committingTimeWindow + 1);

// expect revert when trying to reveal a vote using the incorrect salt
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_WrongRevealData.selector);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, bytes32('bad-salt'));

// succesfully reveal a vote
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

// warp past the revealing phase
vm.warp(block.timestamp + _revealingTimeWindow);

// expect revert when trying to reveal a vote phase the revealing phase
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_RevealingPhaseOver.selector);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

vm.stopPrank();
}

function test_multipleVoters() public {
oracle.escalateDispute(mockRequest, mockResponse, mockDispute);

uint256 _votes = 10;

// expect call to transfer `0` tokens to voterB
vm.expectCall(address(_votingToken), abi.encodeCall(IERC20.transfer, (_voterA, _votes)), 1);
vm.expectCall(address(_votingToken), abi.encodeCall(IERC20.transfer, (_voterB, _votes)), 1);

// voterA cast votes
deal(address(_votingToken), _voterA, _votes);
vm.startPrank(_voterA);
_votingToken.approve(address(privateERC20ResolutionModule), _votes);
privateERC20ResolutionModule.commitVote(
mockRequest, mockDispute, privateERC20ResolutionModule.computeCommitment(_disputeId, _votes, _goodSalt)
);
vm.stopPrank();

// voterB cast votes
deal(address(_votingToken), _voterB, _votes);
vm.startPrank(_voterB);
_votingToken.approve(address(privateERC20ResolutionModule), _votes);
privateERC20ResolutionModule.commitVote(
mockRequest, mockDispute, privateERC20ResolutionModule.computeCommitment(_disputeId, _votes, _goodSalt)
);
vm.stopPrank();

// warp past the voting phase
vm.warp(block.timestamp + _committingTimeWindow + 1);

vm.prank(_voterA);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

// expect revert because the salt is not correct
vm.expectRevert(IPrivateERC20ResolutionModule.PrivateERC20ResolutionModule_WrongRevealData.selector);
vm.prank(_voterB);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, bytes32('bad salt'));

vm.prank(_voterB);
privateERC20ResolutionModule.revealVote(mockRequest, mockDispute, _votes, _goodSalt);

vm.stopPrank();

vm.warp(block.timestamp + _revealingTimeWindow + 1);

oracle.resolveDispute(mockRequest, mockResponse, mockDispute);

// both voters get their votes back
assertEq(_votingToken.balanceOf(_voterA), _votes);
assertEq(_votingToken.balanceOf(_voterB), _votes);
}

function _setupEscalatedDispute() internal {
function _setupDispute() internal {
_resetMockIds();

_createRequest();
_proposeResponse();
_disputeId = _disputeResponse();

oracle.escalateDispute(mockRequest, mockResponse, mockDispute);
}
}
Loading

0 comments on commit d544ba0

Please sign in to comment.