Skip to content

Commit

Permalink
perf: optimize PrivateERC20ResolutionModule (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
gas1cent authored Nov 21, 2023
1 parent 9eda7b0 commit 795ffad
Show file tree
Hide file tree
Showing 7 changed files with 990 additions and 973 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ The `PrivateERC20ResolutionModule` is a contract that allows users to vote on a

### Key methods

- `decodeRequestData(bytes32 _requestId)`: Returns the decoded data for a request.
- `startResolution(bytes32 _disputeId)`: Starts the committing phase for a dispute.
- `commitVote(bytes32 _requestId, bytes32 _disputeId, bytes32 _commitment)`: Stores a commitment for a vote cast by a voter.
- `revealVote(bytes32 _requestId, bytes32 _disputeId, uint256 _numberOfVotes, bytes32 _salt)`: Reveals a vote cast by a voter.
- `resolveDispute(bytes32 _disputeId)`: Resolves a dispute by tallying the votes and executing the winning outcome.
- `computeCommitment(bytes32 _disputeId, uint256 _numberOfVotes, bytes32 _salt)`: Computes a valid commitment for the revealing phase.
- `decodeRequestData`: Returns the decoded data for a request.
- `startResolution`: Starts the committing phase for a dispute.
- `commitVote`: Stores a commitment for a vote cast by a voter.
- `revealVote`: Reveals a vote cast by a voter.
- `resolveDispute`: Resolves a dispute by tallying the votes and executing the winning outcome.
- `computeCommitment`: Computes a valid commitment for the revealing phase.

### Request Parameters

Expand All @@ -36,4 +36,3 @@ The `PrivateERC20ResolutionModule` is a contract that allows users to vote on a
- It is implied that the voters are incentivized to vote either because they're the governing entity of the ERC20 and have a stake in the outcome of the dispute or because they expect to be rewarded by such an entity.
- The `commitVote` function allows committing multiple times and overwriting a previous commitment.
- The `revealVote` function requires the user to have previously approved the module to transfer the tokens.

305 changes: 162 additions & 143 deletions solidity/contracts/modules/resolution/PrivateERC20ResolutionModule.sol
Original file line number Diff line number Diff line change
@@ -1,143 +1,162 @@
// // SPDX-License-Identifier: MIT
// pragma solidity ^0.8.19;

// // solhint-disable-next-line no-unused-import
// import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
// import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
// import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';

// // solhint-disable-next-line no-unused-import
// import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol';
// import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol';

// import {IPrivateERC20ResolutionModule} from '../../../interfaces/modules/resolution/IPrivateERC20ResolutionModule.sol';

// contract PrivateERC20ResolutionModule is Module, IPrivateERC20ResolutionModule {
// using SafeERC20 for IERC20;
// using EnumerableSet for EnumerableSet.AddressSet;

// /// @inheritdoc IPrivateERC20ResolutionModule
// mapping(bytes32 _disputeId => Escalation _escalation) public escalations;
// /**
// * @notice The data of the voters for a given dispute
// */
// mapping(bytes32 _disputeId => mapping(address _voter => VoterData)) internal _votersData;
// /**
// * @notice The voters addresses for a given dispute
// */
// mapping(bytes32 _disputeId => EnumerableSet.AddressSet _votersSet) internal _voters;

// constructor(IOracle _oracle) Module(_oracle) {}

// /// @inheritdoc IModule
// function moduleName() external pure returns (string memory _moduleName) {
// return 'PrivateERC20ResolutionModule';
// }

// /// @inheritdoc IPrivateERC20ResolutionModule
// function decodeRequestData(bytes32 _requestId) public view returns (RequestParameters memory _params) {
// _params = abi.decode(requestData[_requestId], (RequestParameters));
// }

// /// @inheritdoc IPrivateERC20ResolutionModule
// function startResolution(bytes32 _disputeId) external onlyOracle {
// escalations[_disputeId].startTime = block.timestamp;
// emit CommittingPhaseStarted(block.timestamp, _disputeId);
// }

// /// @inheritdoc IPrivateERC20ResolutionModule
// function commitVote(bytes32 _requestId, bytes32 _disputeId, bytes32 _commitment) public {
// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId);
// if (_dispute.createdAt == 0) revert PrivateERC20ResolutionModule_NonExistentDispute();
// if (_dispute.status != IOracle.DisputeStatus.None) revert PrivateERC20ResolutionModule_AlreadyResolved();

// uint256 _startTime = escalations[_disputeId].startTime;
// if (_startTime == 0) revert PrivateERC20ResolutionModule_DisputeNotEscalated();

// RequestParameters memory _params = decodeRequestData(_requestId);
// uint256 _committingDeadline = _startTime + _params.committingTimeWindow;
// if (block.timestamp >= _committingDeadline) revert PrivateERC20ResolutionModule_CommittingPhaseOver();

// if (_commitment == bytes32('')) revert PrivateERC20ResolutionModule_EmptyCommitment();
// _votersData[_disputeId][msg.sender] = VoterData({numOfVotes: 0, commitment: _commitment});

// emit VoteCommitted(msg.sender, _disputeId, _commitment);
// }

// /// @inheritdoc IPrivateERC20ResolutionModule
// function revealVote(bytes32 _requestId, bytes32 _disputeId, uint256 _numberOfVotes, bytes32 _salt) public {
// Escalation memory _escalation = escalations[_disputeId];
// if (_escalation.startTime == 0) revert PrivateERC20ResolutionModule_DisputeNotEscalated();

// RequestParameters memory _params = decodeRequestData(_requestId);
// (uint256 _revealStartTime, uint256 _revealEndTime) = (
// _escalation.startTime + _params.committingTimeWindow,
// _escalation.startTime + _params.committingTimeWindow + _params.revealingTimeWindow
// );
// if (block.timestamp <= _revealStartTime) revert PrivateERC20ResolutionModule_OnGoingCommittingPhase();
// if (block.timestamp > _revealEndTime) revert PrivateERC20ResolutionModule_RevealingPhaseOver();

// VoterData storage _voterData = _votersData[_disputeId][msg.sender];

// if (_voterData.commitment != keccak256(abi.encode(msg.sender, _disputeId, _numberOfVotes, _salt))) {
// revert PrivateERC20ResolutionModule_WrongRevealData();
// }

// _voterData.numOfVotes = _numberOfVotes;
// _voterData.commitment = bytes32('');
// _voters[_disputeId].add(msg.sender);
// escalations[_disputeId].totalVotes += _numberOfVotes;

// _params.votingToken.safeTransferFrom(msg.sender, address(this), _numberOfVotes);

// emit VoteRevealed(msg.sender, _disputeId, _numberOfVotes);
// }

// /// @inheritdoc IPrivateERC20ResolutionModule
// function resolveDispute(bytes32 _disputeId) external onlyOracle {
// IOracle.Dispute memory _dispute = ORACLE.getDispute(_disputeId);
// if (_dispute.createdAt == 0) revert PrivateERC20ResolutionModule_NonExistentDispute();
// if (_dispute.status != IOracle.DisputeStatus.None) revert PrivateERC20ResolutionModule_AlreadyResolved();

// Escalation memory _escalation = escalations[_disputeId];
// if (_escalation.startTime == 0) revert PrivateERC20ResolutionModule_DisputeNotEscalated();

// RequestParameters memory _params = decodeRequestData(_dispute.requestId);

// if (block.timestamp < _escalation.startTime + _params.committingTimeWindow) {
// revert PrivateERC20ResolutionModule_OnGoingCommittingPhase();
// }
// if (block.timestamp < _escalation.startTime + _params.committingTimeWindow + _params.revealingTimeWindow) {
// revert PrivateERC20ResolutionModule_OnGoingRevealingPhase();
// }

// uint256 _quorumReached = _escalation.totalVotes >= _params.minVotesForQuorum ? 1 : 0;

// address[] memory __voters = _voters[_disputeId].values();

// if (_quorumReached == 1) {
// ORACLE.updateDisputeStatus(_disputeId, IOracle.DisputeStatus.Won);
// emit DisputeResolved(_dispute.requestId, _disputeId, IOracle.DisputeStatus.Won);
// } else {
// ORACLE.updateDisputeStatus(_disputeId, IOracle.DisputeStatus.Lost);
// emit DisputeResolved(_dispute.requestId, _disputeId, IOracle.DisputeStatus.Lost);
// }

// uint256 _length = __voters.length;
// for (uint256 _i; _i < _length;) {
// _params.votingToken.safeTransfer(__voters[_i], _votersData[_disputeId][__voters[_i]].numOfVotes);
// unchecked {
// ++_i;
// }
// }
// }

// /// @inheritdoc IPrivateERC20ResolutionModule
// function computeCommitment(
// bytes32 _disputeId,
// uint256 _numberOfVotes,
// bytes32 _salt
// ) external view returns (bytes32 _commitment) {
// _commitment = keccak256(abi.encode(msg.sender, _disputeId, _numberOfVotes, _salt));
// }
// }
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

// solhint-disable-next-line no-unused-import
import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol';
import {SafeERC20} from '@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol';
import {EnumerableSet} from '@openzeppelin/contracts/utils/structs/EnumerableSet.sol';

// solhint-disable-next-line no-unused-import
import {Module, IModule} from '@defi-wonderland/prophet-core-contracts/solidity/contracts/Module.sol';
import {IOracle} from '@defi-wonderland/prophet-core-contracts/solidity/interfaces/IOracle.sol';

import {IPrivateERC20ResolutionModule} from '../../../interfaces/modules/resolution/IPrivateERC20ResolutionModule.sol';

contract PrivateERC20ResolutionModule is Module, IPrivateERC20ResolutionModule {
using SafeERC20 for IERC20;
using EnumerableSet for EnumerableSet.AddressSet;

/// @inheritdoc IPrivateERC20ResolutionModule
mapping(bytes32 _disputeId => Escalation _escalation) public escalations;
/**
* @notice The data of the voters for a given dispute
*/
mapping(bytes32 _disputeId => mapping(address _voter => VoterData)) internal _votersData;
/**
* @notice The voters addresses for a given dispute
*/
mapping(bytes32 _disputeId => EnumerableSet.AddressSet _votersSet) internal _voters;

constructor(IOracle _oracle) Module(_oracle) {}

/// @inheritdoc IModule
function moduleName() external pure returns (string memory _moduleName) {
return 'PrivateERC20ResolutionModule';
}

/// @inheritdoc IPrivateERC20ResolutionModule
function decodeRequestData(bytes calldata _data) public pure returns (RequestParameters memory _params) {
_params = abi.decode(_data, (RequestParameters));
}

/// @inheritdoc IPrivateERC20ResolutionModule
function startResolution(
bytes32 _disputeId,
IOracle.Request calldata _request,

Check warning on line 45 in solidity/contracts/modules/resolution/PrivateERC20ResolutionModule.sol

View workflow job for this annotation

GitHub Actions / Run Linters (16.x)

Variable "_request" is unused
IOracle.Response calldata _response,
IOracle.Dispute calldata _dispute
) external onlyOracle {
escalations[_disputeId].startTime = block.timestamp;
emit CommittingPhaseStarted(block.timestamp, _disputeId);
}

/// @inheritdoc IPrivateERC20ResolutionModule
function commitVote(IOracle.Request calldata _request, IOracle.Dispute calldata _dispute, bytes32 _commitment) public {
bytes32 _disputeId = _getId(_dispute);
if (ORACLE.createdAt(_disputeId) == 0) revert PrivateERC20ResolutionModule_NonExistentDispute();
if (ORACLE.disputeStatus(_disputeId) != IOracle.DisputeStatus.None) {
revert PrivateERC20ResolutionModule_AlreadyResolved();
}

uint256 _startTime = escalations[_disputeId].startTime;
if (_startTime == 0) revert PrivateERC20ResolutionModule_DisputeNotEscalated();

RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData);
uint256 _committingDeadline = _startTime + _params.committingTimeWindow;
if (block.timestamp >= _committingDeadline) revert PrivateERC20ResolutionModule_CommittingPhaseOver();

if (_commitment == bytes32('')) revert PrivateERC20ResolutionModule_EmptyCommitment();
_votersData[_disputeId][msg.sender] = VoterData({numOfVotes: 0, commitment: _commitment});

emit VoteCommitted(msg.sender, _disputeId, _commitment);
}

/// @inheritdoc IPrivateERC20ResolutionModule
function revealVote(
IOracle.Request calldata _request,
IOracle.Dispute calldata _dispute,
uint256 _numberOfVotes,
bytes32 _salt
) public {
bytes32 _disputeId = _getId(_dispute);
Escalation memory _escalation = escalations[_disputeId];
if (_escalation.startTime == 0) revert PrivateERC20ResolutionModule_DisputeNotEscalated();

RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData);
(uint256 _revealStartTime, uint256 _revealEndTime) = (
_escalation.startTime + _params.committingTimeWindow,
_escalation.startTime + _params.committingTimeWindow + _params.revealingTimeWindow
);
if (block.timestamp <= _revealStartTime) revert PrivateERC20ResolutionModule_OnGoingCommittingPhase();
if (block.timestamp > _revealEndTime) revert PrivateERC20ResolutionModule_RevealingPhaseOver();

VoterData storage _voterData = _votersData[_disputeId][msg.sender];

if (_voterData.commitment != keccak256(abi.encode(msg.sender, _disputeId, _numberOfVotes, _salt))) {
revert PrivateERC20ResolutionModule_WrongRevealData();
}

_voterData.numOfVotes = _numberOfVotes;
_voterData.commitment = bytes32('');
_voters[_disputeId].add(msg.sender);
escalations[_disputeId].totalVotes += _numberOfVotes;

_params.votingToken.safeTransferFrom(msg.sender, address(this), _numberOfVotes);

emit VoteRevealed(msg.sender, _disputeId, _numberOfVotes);
}

/// @inheritdoc IPrivateERC20ResolutionModule
function resolveDispute(
bytes32 _disputeId,
IOracle.Request calldata _request,
IOracle.Response calldata _response,
IOracle.Dispute calldata _dispute
) external onlyOracle {
if (ORACLE.createdAt(_disputeId) == 0) revert PrivateERC20ResolutionModule_NonExistentDispute();
if (ORACLE.disputeStatus(_disputeId) != IOracle.DisputeStatus.None) {
revert PrivateERC20ResolutionModule_AlreadyResolved();
}

Escalation memory _escalation = escalations[_disputeId];
if (_escalation.startTime == 0) revert PrivateERC20ResolutionModule_DisputeNotEscalated();

RequestParameters memory _params = decodeRequestData(_request.resolutionModuleData);

if (block.timestamp < _escalation.startTime + _params.committingTimeWindow) {
revert PrivateERC20ResolutionModule_OnGoingCommittingPhase();
}
if (block.timestamp < _escalation.startTime + _params.committingTimeWindow + _params.revealingTimeWindow) {
revert PrivateERC20ResolutionModule_OnGoingRevealingPhase();
}

uint256 _quorumReached = _escalation.totalVotes >= _params.minVotesForQuorum ? 1 : 0;

address[] memory __voters = _voters[_disputeId].values();

if (_quorumReached == 1) {
ORACLE.updateDisputeStatus(_request, _response, _dispute, IOracle.DisputeStatus.Won);
emit DisputeResolved(_dispute.requestId, _disputeId, IOracle.DisputeStatus.Won);
} else {
ORACLE.updateDisputeStatus(_request, _response, _dispute, IOracle.DisputeStatus.Lost);
emit DisputeResolved(_dispute.requestId, _disputeId, IOracle.DisputeStatus.Lost);
}

uint256 _length = __voters.length;
for (uint256 _i; _i < _length;) {
_params.votingToken.safeTransfer(__voters[_i], _votersData[_disputeId][__voters[_i]].numOfVotes);
unchecked {
++_i;
}
}
}

/// @inheritdoc IPrivateERC20ResolutionModule
function computeCommitment(
bytes32 _disputeId,
uint256 _numberOfVotes,
bytes32 _salt
) external view returns (bytes32 _commitment) {
_commitment = keccak256(abi.encode(msg.sender, _disputeId, _numberOfVotes, _salt));
}
}
Loading

0 comments on commit 795ffad

Please sign in to comment.