Skip to content

Commit fe3126d

Browse files
committed
init voting concept
Signed-off-by: avichalp <[email protected]>
1 parent 8dbfadf commit fe3126d

File tree

2 files changed

+282
-0
lines changed

2 files changed

+282
-0
lines changed

src/token/Voting.sol

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.26;
3+
4+
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
5+
6+
contract Voting is Ownable(msg.sender) {
7+
// Vote allocation structure
8+
struct VoteAllocation {
9+
uint256 voteCount;
10+
uint256 expiryTimestamp;
11+
}
12+
13+
// Mapping to track vote allocations by address
14+
mapping(address => VoteAllocation) public voteAllocations;
15+
16+
// Week duration in seconds (7 days)
17+
uint256 private constant WEEK_DURATION = 7 * 24 * 60 * 60;
18+
19+
// Events
20+
event VotesAllocated(address indexed voter, uint256 voteCount, uint256 expiryTimestamp);
21+
event VoteCast(address indexed voter, uint256 remainingVotes);
22+
23+
/**
24+
* @notice Allocate votes to an address
25+
* @param voter Address to allocate votes to
26+
* @param voteCount Number of votes to allocate
27+
*/
28+
function allocateVotes(address voter, uint256 voteCount) external onlyOwner {
29+
// Set expiry timestamp to the end of the current week
30+
uint256 expiryTimestamp = block.timestamp + WEEK_DURATION;
31+
32+
// Overwrite any existing allocation
33+
voteAllocations[voter] = VoteAllocation({
34+
voteCount: voteCount,
35+
expiryTimestamp: expiryTimestamp
36+
});
37+
38+
emit VotesAllocated(voter, voteCount, expiryTimestamp);
39+
}
40+
41+
/**
42+
* @notice Cast a vote
43+
* @dev Reduces the caller's vote count by 1 if they have unexpired votes
44+
* @return success Whether the vote was successfully cast
45+
*/
46+
function vote() external returns (bool success) {
47+
VoteAllocation storage allocation = voteAllocations[msg.sender];
48+
49+
// Check if votes are still valid
50+
require(block.timestamp < allocation.expiryTimestamp, "Votes expired");
51+
require(allocation.voteCount > 0, "No votes available");
52+
53+
// Reduce vote count
54+
allocation.voteCount -= 1;
55+
56+
emit VoteCast(msg.sender, allocation.voteCount);
57+
return true;
58+
}
59+
60+
/**
61+
* @notice Check if an address has votes remaining
62+
* @param voter Address to check
63+
* @return hasVotes Whether the address has unexpired votes remaining
64+
*/
65+
function hasVotes(address voter) external view returns (bool hasVotes) {
66+
VoteAllocation memory allocation = voteAllocations[voter];
67+
return allocation.voteCount > 0 && block.timestamp < allocation.expiryTimestamp;
68+
}
69+
70+
/**
71+
* @notice Get the remaining vote count for an address
72+
* @param voter Address to check
73+
* @return The number of votes remaining (0 if expired)
74+
*/
75+
function getRemainingVotes(address voter) external view returns (uint256) {
76+
VoteAllocation memory allocation = voteAllocations[voter];
77+
78+
// Return 0 if votes have expired
79+
if (block.timestamp >= allocation.expiryTimestamp) {
80+
return 0;
81+
}
82+
83+
return allocation.voteCount;
84+
}
85+
}

test/Voting.t.sol

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.26;
3+
4+
import {Test, console} from "forge-std/Test.sol";
5+
import {Voting} from "../src/token/Voting.sol";
6+
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
7+
8+
contract VotingTest is Test {
9+
Voting public voting;
10+
address public owner;
11+
address public alice;
12+
address public bob;
13+
14+
function setUp() public {
15+
owner = address(this);
16+
alice = makeAddr("alice");
17+
bob = makeAddr("bob");
18+
19+
voting = new Voting();
20+
21+
// Fund test accounts
22+
vm.deal(alice, 10 ether);
23+
vm.deal(bob, 10 ether);
24+
}
25+
26+
function testOwnership() public view {
27+
assertEq(voting.owner(), owner);
28+
}
29+
30+
function testAllocateVotes() public {
31+
// Allocate 5 votes to Alice
32+
voting.allocateVotes(alice, 5);
33+
34+
// Check allocation
35+
(uint256 voteCount, uint256 expiryTimestamp) = voting.voteAllocations(alice);
36+
assertEq(voteCount, 5, "Alice's vote count should be 5");
37+
38+
// Check timestamp (roughly)
39+
uint256 expectedExpiry = block.timestamp + 7 days;
40+
assertApproxEqAbs(expiryTimestamp, expectedExpiry, 1, "Expiry should be about a week from now");
41+
}
42+
43+
function testAllocateVotesRequiresOwner() public {
44+
// Try to allocate votes as Alice (not owner)
45+
vm.prank(alice);
46+
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice));
47+
voting.allocateVotes(bob, 5);
48+
}
49+
50+
function testVoting() public {
51+
// Allocate 3 votes to Alice
52+
voting.allocateVotes(alice, 3);
53+
54+
// Alice votes
55+
vm.prank(alice);
56+
bool success = voting.vote();
57+
assertTrue(success, "Vote should succeed");
58+
59+
// Check remaining votes
60+
(uint256 voteCount, ) = voting.voteAllocations(alice);
61+
assertEq(voteCount, 2, "Alice should have 2 votes remaining");
62+
63+
// Vote again
64+
vm.prank(alice);
65+
success = voting.vote();
66+
assertTrue(success, "Second vote should succeed");
67+
68+
// Check remaining votes again
69+
(voteCount, ) = voting.voteAllocations(alice);
70+
assertEq(voteCount, 1, "Alice should have 1 vote remaining");
71+
}
72+
73+
function testCannotVoteWithoutAllocation() public {
74+
// Bob tries to vote without allocation
75+
vm.prank(bob);
76+
// For an address with no allocation, the expiryTimestamp is 0, so it's actually expired
77+
vm.expectRevert("Votes expired");
78+
voting.vote();
79+
}
80+
81+
function testCannotVoteAfterDepleted() public {
82+
// Allocate 1 vote to Alice
83+
voting.allocateVotes(alice, 1);
84+
85+
// Alice votes once
86+
vm.prank(alice);
87+
voting.vote();
88+
89+
// Try to vote again
90+
vm.prank(alice);
91+
vm.expectRevert("No votes available");
92+
voting.vote();
93+
}
94+
95+
function testVoteExpiration() public {
96+
// Allocate 5 votes to Alice
97+
voting.allocateVotes(alice, 5);
98+
99+
// Move time forward one week + 1 second
100+
vm.warp(block.timestamp + 7 days + 1 seconds);
101+
102+
// Alice tries to vote after expiration
103+
vm.prank(alice);
104+
vm.expectRevert("Votes expired");
105+
voting.vote();
106+
}
107+
108+
function testHasVotes() public {
109+
// Allocate 3 votes to Alice
110+
voting.allocateVotes(alice, 3);
111+
112+
// Check if Alice has votes
113+
bool hasVotes = voting.hasVotes(alice);
114+
assertTrue(hasVotes, "Alice should have votes");
115+
116+
// Bob should not have votes
117+
hasVotes = voting.hasVotes(bob);
118+
assertFalse(hasVotes, "Bob should not have votes");
119+
120+
// Alice votes all 3 times
121+
vm.startPrank(alice);
122+
voting.vote();
123+
voting.vote();
124+
voting.vote();
125+
vm.stopPrank();
126+
127+
// Alice should not have votes anymore
128+
hasVotes = voting.hasVotes(alice);
129+
assertFalse(hasVotes, "Alice should not have votes after using all");
130+
}
131+
132+
function testGetRemainingVotes() public {
133+
// Allocate 5 votes to Alice
134+
voting.allocateVotes(alice, 5);
135+
136+
// Check remaining votes
137+
uint256 remaining = voting.getRemainingVotes(alice);
138+
assertEq(remaining, 5, "Alice should have 5 votes");
139+
140+
// Alice votes twice
141+
vm.startPrank(alice);
142+
voting.vote();
143+
voting.vote();
144+
vm.stopPrank();
145+
146+
// Check remaining votes again
147+
remaining = voting.getRemainingVotes(alice);
148+
assertEq(remaining, 3, "Alice should have 3 votes remaining");
149+
150+
// Move time forward past expiration
151+
vm.warp(block.timestamp + 7 days + 1 seconds);
152+
153+
// Check remaining votes after expiration
154+
remaining = voting.getRemainingVotes(alice);
155+
assertEq(remaining, 0, "Expired votes should return 0");
156+
}
157+
158+
function testReallocation() public {
159+
// Allocate 3 votes to Alice
160+
voting.allocateVotes(alice, 3);
161+
162+
// Alice uses 1 vote
163+
vm.prank(alice);
164+
voting.vote();
165+
166+
// Check remaining
167+
uint256 remaining = voting.getRemainingVotes(alice);
168+
assertEq(remaining, 2, "Alice should have 2 votes remaining");
169+
170+
// Reallocate 5 votes to Alice
171+
voting.allocateVotes(alice, 5);
172+
173+
// Check new allocation
174+
remaining = voting.getRemainingVotes(alice);
175+
assertEq(remaining, 5, "Alice should have 5 votes after reallocation");
176+
}
177+
178+
function testMultipleUsers() public {
179+
// Allocate votes to Alice and Bob
180+
voting.allocateVotes(alice, 3);
181+
voting.allocateVotes(bob, 2);
182+
183+
// Both vote
184+
vm.prank(alice);
185+
voting.vote();
186+
187+
vm.prank(bob);
188+
voting.vote();
189+
190+
// Check remaining
191+
uint256 aliceVotes = voting.getRemainingVotes(alice);
192+
uint256 bobVotes = voting.getRemainingVotes(bob);
193+
194+
assertEq(aliceVotes, 2, "Alice should have 2 votes remaining");
195+
assertEq(bobVotes, 1, "Bob should have 1 vote remaining");
196+
}
197+
}

0 commit comments

Comments
 (0)