Skip to content

Commit

Permalink
style
Browse files Browse the repository at this point in the history
  • Loading branch information
NicoAcosta committed Oct 12, 2024
1 parent 0cf4854 commit 7738767
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 11 deletions.
30 changes: 23 additions & 7 deletions src/voting/FixedQuestion.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,23 @@
pragma solidity ^0.8.20;

import { Question } from "./Question.sol";
import { IFixedQuestion, IQuestion } from "./interfaces/IFixedQuestion.sol";

contract FixedQuestion is Question {
// Mapping to store user votes
/// @title Fixed Question Contract for Voting System
/// @dev Implements a fixed-choice voting system where users can only vote once
/// @notice This contract allows users to vote on predefined options, with each user limited to one vote
contract FixedQuestion is Question, IFixedQuestion {
// Mapping to store user votes: user address => option ID
mapping(address => uint256) private userVotes;

// Custom errors
error UserAlreadyVoted();

/// @notice Constructor to initialize the fixed question
/// @dev Sets up the initial state and options for the fixed question
/// @param initialOwner The address of the initial owner of the contract
/// @param _title The title of the question
/// @param _description The description of the question
/// @param _deadline The deadline for voting
/// @param _pointsAddress The address of the Points contract
/// @param initialOptions An array of initial options for the question
constructor(
address initialOwner,
string memory _title,
Expand All @@ -19,20 +28,27 @@ contract FixedQuestion is Question {
Option[] memory initialOptions
) Question(initialOwner, _title, _description, _deadline, _pointsAddress) {
questionType = QuestionType.Fixed;
// Add initial options
for (uint256 i = 0; i < initialOptions.length; i++) {
_addOption(initialOptions[i].title, initialOptions[i].description);
}
}

/// @inheritdoc Question
function _processVote(uint256 optionId) internal override {
// Implement voting logic
// Check if the user has already voted
if (userVotes[msg.sender] != 0) {
revert UserAlreadyVoted();
}
// Record the user's vote
userVotes[msg.sender] = optionId;
}

function hasVoted(address voter, uint256 optionId) public view override returns (bool) {
/// @inheritdoc Question
function hasVoted(
address voter,
uint256 optionId
) public view override(IQuestion, Question) returns (bool) {
return userVotes[voter] == optionId;
}
}
79 changes: 79 additions & 0 deletions src/voting/OpenQuestion.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { Question } from "./Question.sol";
import { IOpenQuestion, IQuestion } from "./interfaces/IOpenQuestion.sol";

/// @title OpenQuestion Contract
/// @dev Implements an open-ended question where users can add options and vote
contract OpenQuestion is Question, IOpenQuestion {
// Mapping to store user votes for each option
mapping(address voter => mapping(uint256 optionId => bool hasVoted)) private userVotes;

/// @inheritdoc IOpenQuestion
/// @notice Minimum points required to add an option
uint256 public override minPointsToAddOption;

/// @notice Initializes the OpenQuestion contract
/// @dev Sets up the question details and minimum points required to add an option
/// @param initialOwner The address of the initial owner of the question
/// @param _title The title of the question
/// @param _description The description of the question
/// @param _deadline The deadline for voting on the question
/// @param _pointsAddress The address of the Points contract
/// @param _minPointsToAddOption The minimum points required to add a new option
constructor(
address initialOwner,
string memory _title,
string memory _description,
uint256 _deadline,
address _pointsAddress,
uint256 _minPointsToAddOption
) Question(initialOwner, _title, _description, _deadline, _pointsAddress) {
questionType = QuestionType.Open;
minPointsToAddOption = _minPointsToAddOption;
}

/// @inheritdoc IOpenQuestion
/// @notice Adds a new option to the question
/// @dev Checks if the user has sufficient points to add the option
/// @param _title The title of the new option
/// @param _description The description of the new option
function addOption(string memory _title, string memory _description) external override {
if (points.balanceAtTimestamp(msg.sender, deadline) < minPointsToAddOption) {
revert InsufficientPoints();
}
_addOption(_title, _description);
}

/// @notice Processes a vote for a specific option
/// @dev Overrides the base _processVote function to check for duplicate votes
/// @param optionId The ID of the option being voted for
function _processVote(uint256 optionId) internal override {
if (userVotes[msg.sender][optionId]) {
revert UserAlreadyVotedThisOption(msg.sender, optionId);
}
userVotes[msg.sender][optionId] = true;
}

/// @inheritdoc IQuestion
/// @notice Checks if a user has voted for a specific option
/// @dev Returns true if the user has voted for the option, false otherwise
/// @param voter The address of the user
/// @param optionId The ID of the option
function hasVoted(
address voter,
uint256 optionId
) public view override(IQuestion, Question) returns (bool) {
return userVotes[voter][optionId];
}

/// @inheritdoc IOpenQuestion
/// @notice Updates the minimum points required to add a new option
/// @dev Only callable by the owner
/// @param _minPointsToAddOption The new minimum points required
function updateMinPointsToAddOption(uint256 _minPointsToAddOption) external override onlyOwner {
minPointsToAddOption = _minPointsToAddOption;
emit MinPointsToAddOptionUpdated(_minPointsToAddOption);
}
}
28 changes: 24 additions & 4 deletions src/voting/Question.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
pragma solidity ^0.8.20;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IQuestion } from "./interfaces/IQuestion.sol";
import { IPoints } from "../points/interfaces/IPoints.sol";

/// @title Abstract Question Contract for Voting System
/// @dev This contract implements the base functionality for a voting question
/// @notice This contract allows users to vote on options and manages the voting process
abstract contract Question is Ownable, IQuestion {
// State variables
uint256 public immutable deploymentTime;
Expand All @@ -21,7 +23,13 @@ abstract contract Question is Ownable, IQuestion {
// Mapping to store points accrued for each option
mapping(uint256 optionId => uint256 points) public optionPointsAccrued;

// Constructor
/// @notice Constructor to initialize the question
/// @dev Sets up the initial state of the question
/// @param initialOwner The address of the initial owner of the contract
/// @param _title The title of the question
/// @param _description The description of the question
/// @param _deadline The deadline for voting
/// @param _pointsAddress The address of the Points contract
constructor(
address initialOwner,
string memory _title,
Expand All @@ -39,7 +47,7 @@ abstract contract Question is Ownable, IQuestion {
options.push(Option("", "", msg.sender));
}

// External functions
/// @inheritdoc IQuestion
function vote(uint256 optionId) external {
if (block.timestamp >= deadline) revert VotingEnded();
if (optionId == 0 || optionId > options.length) revert InvalidOption();
Expand All @@ -54,31 +62,36 @@ abstract contract Question is Ownable, IQuestion {
emit Voted(msg.sender, optionId, timestamp);
}

/// @inheritdoc IQuestion
function updateTitle(string memory _title) external onlyOwner {
title = _title;
emit QuestionUpdated(_title, description, deadline);
}

/// @inheritdoc IQuestion
function updateDescription(string memory _description) external onlyOwner {
description = _description;
emit QuestionUpdated(title, _description, deadline);
}

/// @inheritdoc IQuestion
function updateDeadline(uint256 _deadline) external onlyOwner {
deadline = _deadline;
emit QuestionUpdated(title, description, _deadline);
}

// External view functions
/// @inheritdoc IQuestion
function getOptions() external view returns (Option[] memory) {
return options;
}

/// @inheritdoc IQuestion
function getOption(uint256 optionId) external view returns (Option memory) {
if (optionId >= options.length) revert InvalidOption();
return options[optionId];
}

/// @inheritdoc IQuestion
function getQuestionView(address user) external view returns (QuestionView memory) {
uint256 totalVotes = 0;
// Adjust the array size to exclude the empty option at index 0
Expand Down Expand Up @@ -115,14 +128,20 @@ abstract contract Question is Ownable, IQuestion {

// Internal functions

/// @dev Processes a vote for a specific option
/// @param optionId The ID of the option being voted for
function _processVote(uint256 optionId) internal virtual;

/// @dev Adds a new option to the question
/// @param _title The title of the new option
/// @param _description The description of the new option
function _addOption(string memory _title, string memory _description) internal {
uint256 optionId = options.length;
options.push(Option(_title, _description, msg.sender));
emit NewOption(msg.sender, optionId, _title);
}

/// @inheritdoc IQuestion
function getStatus() public view returns (Status) {
if (block.timestamp < deadline) {
return Status.Active;
Expand All @@ -131,5 +150,6 @@ abstract contract Question is Ownable, IQuestion {
}
}

/// @inheritdoc IQuestion
function hasVoted(address voter, uint256 optionId) public view virtual returns (bool);
}
13 changes: 13 additions & 0 deletions src/voting/interfaces/IFixedQuestion.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { IQuestion } from "./IQuestion.sol";

/// @title IFixedQuestion Interface
/// @notice Interface for fixed-choice voting questions
/// @dev Extends the IQuestion interface with specific functionality for fixed-choice questions
interface IFixedQuestion is IQuestion {
/// @notice Error thrown when a user attempts to vote more than once
/// @dev This error should be used in the implementation to prevent multiple votes from the same user
error UserAlreadyVoted();
}
35 changes: 35 additions & 0 deletions src/voting/interfaces/IOpenQuestion.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import { IQuestion } from "./IQuestion.sol";

/// @title IOpenQuestion Interface
/// @dev Interface for an open-ended question where users can add new options
interface IOpenQuestion is IQuestion {
/// @notice Thrown when a user tries to add an option without sufficient points
error InsufficientPoints();

/// @notice Thrown when a user tries to vote for an option they've already voted for
/// @param voter The address of the voter
/// @param optionId The ID of the option
error UserAlreadyVotedThisOption(address voter, uint256 optionId);

/// @notice Emitted when the minimum points required to add an option is updated
/// @param newMinPoints The new minimum points value
event MinPointsToAddOptionUpdated(uint256 newMinPoints);

/// @notice Returns the minimum points required to add a new option
/// @return The minimum points required
function minPointsToAddOption() external view returns (uint256);

/// @notice Adds a new option to the question
/// @dev Requires the caller to have sufficient points
/// @param _title The title of the new option
/// @param _description The description of the new option
function addOption(string memory _title, string memory _description) external;

/// @notice Updates the minimum points required to add a new option
/// @dev Can only be called by the contract owner or authorized role
/// @param _minPointsToAddOption The new minimum points value
function updateMinPointsToAddOption(uint256 _minPointsToAddOption) external;
}
Loading

0 comments on commit 7738767

Please sign in to comment.