mirror of
https://github.com/status-im/dappconnect-vote-poll-sdk.git
synced 2025-02-10 12:16:25 +00:00
Merge pull request #17 from status-im/voting-contract
This commit is contained in:
commit
60e17d454c
@ -1,41 +1,39 @@
|
||||
# status-waku-voting/contracts
|
||||
Status white label proposal contract
|
||||
# wakuconnect-voting-sdk/contracts
|
||||
|
||||
WakuConnect Proposal Contracts.
|
||||
|
||||
## Voting Contract
|
||||
|
||||
Voting contract is a smart contract created for purpose of
|
||||
having smart contract that can create and save results of proposals.
|
||||
The Voting Contract is a smart contract that enables the storage of proposals, from creation to vote result.
|
||||
|
||||
This Contract is responsible for creating voting rooms in which you can vote for or against them.
|
||||
Lifecycle of proposal:
|
||||
|
||||
Lifecycle of voting room:
|
||||
1. initialize voting room.
|
||||
2. period of time when votes are accepted.
|
||||
3. voting time is finished votes are no longer accepted.
|
||||
1. Initialize proposal,
|
||||
2. Proposal is open, votes are accepted,
|
||||
3. Voting time ends, votes are no longer accepted, result is final.
|
||||
|
||||
### Types
|
||||
|
||||
Main types used for voting are:
|
||||
The main types used in the contract are:
|
||||
|
||||
- `VotingRoom`
|
||||
- `Proposal`
|
||||
|
||||
Is a type that hold information about voting room for a given proposal
|
||||
Holds information about the proposal
|
||||
|
||||
#### Fields
|
||||
```solidity
|
||||
// block at which room was created
|
||||
uint256 startBlock;
|
||||
// block at which the proposal was created
|
||||
uint256 startBlock;
|
||||
// timestamp in seconds after which new votes won't be accepted
|
||||
// when casting votes endAt is compared to block.timestamp
|
||||
uint256 endAt;
|
||||
// question of a proposal which voting room describes
|
||||
string question;
|
||||
// description for proposal
|
||||
uint256 endAt;
|
||||
// proposal title
|
||||
string title;
|
||||
// proposal description
|
||||
string description;
|
||||
// amount of summed votes for
|
||||
// total number of votes in favour of the proposal, this may be weighted
|
||||
uint256 totalVotesFor;
|
||||
// amount of summed votes against
|
||||
// total number of votes against the proposal, this may be weighted
|
||||
uint256 totalVotesAgainst;
|
||||
// list of addresses that already voted
|
||||
address[] voters;
|
||||
@ -43,19 +41,19 @@ Main types used for voting are:
|
||||
|
||||
- `Vote`
|
||||
|
||||
Is a type that hold information about vote for a given voting room
|
||||
Holds the information for one vote
|
||||
|
||||
#### Fields
|
||||
```solidity
|
||||
//address of a voter
|
||||
// address of the voter
|
||||
address voter;
|
||||
// encoded roomId and type
|
||||
// encoded proposalId and type
|
||||
// first bit this field is a vote type:
|
||||
// 1 is a vote for
|
||||
// 0 is a vote against
|
||||
// rest of this field is a roomId shifted one bit to
|
||||
// the left
|
||||
uint256 roomIdAndType;
|
||||
uint256 proposalIdAndType;
|
||||
// amount of token used to vote
|
||||
uint256 tokenAmount;
|
||||
//signature of vote
|
||||
@ -66,31 +64,36 @@ Main types used for voting are:
|
||||
### Constants and variables
|
||||
|
||||
- `token`
|
||||
Variable that holds address of token used for vote verification. It is assigned at contract creation.
|
||||
contract address of the token used for vote verification.
|
||||
It is assigned at contract creation.
|
||||
Only holders of this token can vote.
|
||||
|
||||
- `votingLength`
|
||||
Variable describing length of voting room in seconds
|
||||
Voting length it is assigned at contract creation.
|
||||
- `voteDuration`
|
||||
Length of duration during which proposals can be vote on, in seconds.
|
||||
It is assigned at contract creation.
|
||||
|
||||
- `EIP712DOMAIN_TYPEHASH`
|
||||
Constant holding type hash of EIP712 domain as per EIP712 specification
|
||||
Constant holding type hash of EIP712 domain as per EIP712 specification.
|
||||
|
||||
- `VOTE_TYPEHASH`
|
||||
Constant holding type hash of Vote as per EIP712 specification
|
||||
Constant holding type hash of Vote as per EIP712 specification.
|
||||
|
||||
- `DOMAIN_SEPARATOR`
|
||||
Variable holding hash of domain separator according to EIP712 spec. Assigned at smart contract creation.
|
||||
Variable holding hash of domain separator according to EIP712 spec.
|
||||
It is assigned at smart contract creation.
|
||||
|
||||
- `voted`
|
||||
this variable holds information if given address voted in a given voting room. So it is a mapping of room id to mapping of addresses to bools which say whether or not given address voted.
|
||||
Holds whether a given address has voted to a given proposal.
|
||||
It is a mapping of proposal id to mapping of addresses to booleans which indicates whether the given address has voted.
|
||||
|
||||
- `votingRooms`
|
||||
Array that holds all voting rooms. roomId of voting room is equivalent to its index in array
|
||||
- `proposals`
|
||||
Array that holds all proposals.
|
||||
The id for a proposal is the index in this array.
|
||||
|
||||
### Signing with EIP712
|
||||
|
||||
This smart contract uses EIP712 for signing vote msg.
|
||||
The structure of typed data for vote messages is as follows:
|
||||
EIP712 MUST be used to sign votes.
|
||||
The structure of typed data for a vote message is as follows:
|
||||
```ts
|
||||
{
|
||||
types: {
|
||||
@ -101,7 +104,7 @@ The structure of typed data for vote messages is as follows:
|
||||
{ name: 'verifyingContract', type: 'address' },
|
||||
],
|
||||
Vote: [
|
||||
{ name: 'roomIdAndType', type: 'uint256' },
|
||||
{ name: 'proposalIdAndType', type: 'uint256' },
|
||||
{ name: 'tokenAmount', type: 'uint256' },
|
||||
{ name: 'voter', type: 'address' },
|
||||
],
|
||||
@ -116,7 +119,7 @@ The structure of typed data for vote messages is as follows:
|
||||
message: {
|
||||
voter: voterAddress,
|
||||
tokenAmount: tokenAmount,
|
||||
roomIdAndType: roomIdAndType
|
||||
proposalIdAndType: proposalIdAndType
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -128,66 +131,73 @@ For more information about EIP-712 go to [docs](https://eips.ethereum.org/EIPS/e
|
||||
- `constructor(IERC20 _address)`
|
||||
Assigns `_address` to `token` and generates `DOMAIN_SEPARATOR`
|
||||
|
||||
- `getVotingRooms()`
|
||||
Returns votingRooms
|
||||
- `getProposals()`
|
||||
Returns proposals
|
||||
|
||||
- `getVotingRoomLength()`
|
||||
Returns votingRooms length
|
||||
- `getProposalsLength()`
|
||||
Returns the length of the `proposals` array
|
||||
|
||||
- `getLastNVotingRooms(uint256 amount)`
|
||||
Gets last voting rooms returns amount of voting rooms
|
||||
- `getLastNProposals(uint256 amount)`
|
||||
Returns the N last proposals
|
||||
|
||||
- `getVotingRoomsFrom(uint256 id)`
|
||||
Gets voting rooms from given id
|
||||
- `getProposalFromId(uint256 id)`
|
||||
Gets proposal from given id
|
||||
|
||||
- `getOngoingVotingRooms()`
|
||||
Returns votingRooms in which `room.endAt > block.timestamp` which means the rooms are still accepting votes.
|
||||
Since `votingLength` is set at contract creation and never changed, `room.endAt` is never decreasing with increasing index of votingRoom. Therefore it is enough to check from votingRooms.length up to first element which `endAt < block.timestamp`
|
||||
- `getOpenProposals()`
|
||||
Returns proposals for which `proposal.endAt > block.timestamp` which means the proposals are still accepting votes.
|
||||
Since `proposalDuration` is set at contract creation and never changed,
|
||||
`proposal.endAt` is never decreasing with increasing index of votingRoom.
|
||||
Therefore, it is enough to check from `proposal.length` up to first element which `endAt < block.timestamp`
|
||||
|
||||
- `listRoomVoters(uint256 roomId)`
|
||||
Returns a list of voters for a given voting room. Reverts if roomId doesn't exist.
|
||||
- `listVotersForProposal(uint256 proposalId)`
|
||||
Returns a list of voters for a given proposal.
|
||||
Reverts if there are no proposal for the given `proposalId`.
|
||||
|
||||
- `initializeVotingRoom(string calldata question,string calldata description,uint256 voteAmount)`
|
||||
Creates a new voting room with vote for set to voteAmount.
|
||||
First checks if voter has enough tokens to set vote for.
|
||||
Then creates a new voting room.
|
||||
- `initializeProposal(string calldata title, string calldata description, uint256 creatorVoteForAmount)`
|
||||
Creates a new proposal with the vote of the proposal creator.
|
||||
First checks if the creator voter has enough tokens to set vote for.
|
||||
Then creates a new proposal.
|
||||
`startBlock` is set as current block number.
|
||||
`endAt` is set a current block timestamp plus.`votingLength`.
|
||||
`question` is set as argument `question`.
|
||||
`endAt` is set a current block timestamp plus `votingDuration`.
|
||||
`title` is set as argument `title`.
|
||||
`description` is set as argument `description`.
|
||||
`totalVotesFor` is set as argument `voteAmount`.
|
||||
Mapping `voted` of new voting room id of `msg.sender` is set to true to reflect that message sender has voted on this voting room with `voteAmount`.
|
||||
`creatorVoteForAmount` is set as argument `voteAmount`.
|
||||
Mapping `voted` of new proposal id of `msg.sender` is set to true to reflect that message sender has voted on this voting room with `creatorVoteForAmount`.
|
||||
`votingRooms` are appended with newVotingRoom and `voters` in this new appended element are appended with message sender.
|
||||
After room init `VotingRoomStarted` is emitted.
|
||||
After proposal creation, `ProposalStarted` is emitted.
|
||||
|
||||
- `verify(Vote calldata vote,bytes32 r,bytes32 vs)`
|
||||
- `verify(Vote calldata vote, bytes32 r, bytes32 vs)`
|
||||
Function used to verify that `vote` was signed by `vote.voter` as per EIP712 specification.
|
||||
See [docs](https://eips.ethereum.org/EIPS/eip-712) for more info.
|
||||
|
||||
- `updateRoomVotes(Vote calldata vote, uint256 roomId)`
|
||||
Sets totalVotes amount of voting room with index corresponding to `roomId`.
|
||||
- `castVote(Vote calldata vote, uint256 proposalId)`
|
||||
Cast a single vote.
|
||||
Updates `totalVotes` amount of proposal with index corresponding to `proposalId`.
|
||||
|
||||
If voting first bit of `vote.roomIdAndType` is 1 that means that vote is for and `vote.tokenAmount` is added to `votingRooms[roomId].totalVotesFor`, otherwise if `vote.roomIdAndType` is 0 `vote.tokenAmount` is added to `votingRooms[roomId].totalVotesAgainst`.
|
||||
If voting first bit of `vote.proposalIdAndType` is 1 that means that vote is for and `vote.tokenAmount` is added to `votingRooms[roomId].totalVotesFor`, otherwise if `vote.proposalIdAndType` is 0 `vote.tokenAmount` is added to `votingRooms[roomId].totalVotesAgainst`.
|
||||
|
||||
After that add new address to room `voters` and updates mapping `voted` accordingly.
|
||||
|
||||
- `castVotes(Vote[] calldata votes)`
|
||||
Function used to cast votes in rooms.
|
||||
Function used to cast votes on a single proposas.
|
||||
Function accepts an array of votes of type `Vote`.
|
||||
|
||||
All votes are looped through and verified that votes are:
|
||||
- properly signed
|
||||
- voter has enough tokens to vote
|
||||
- voting room exists
|
||||
- voting room hasn't been closed
|
||||
- proposal exists
|
||||
- proposal hasn't been closed
|
||||
|
||||
Vote verification is as follows.
|
||||
First roomId is decoded from `vote.roomIdAndType` which means shifting it to the right once.
|
||||
Vote verification is as follows:
|
||||
|
||||
First `proposalId` is decoded from `vote.proposalIdAndType` which means shifting it to the right once.
|
||||
|
||||
Then it is verified that voting room with given roomId exists and isn't closed if not whole function reverts, this is to discourage grouping votes for different voting rooms together (! maybe it should be changed so that votes for multiple voting rooms can be cast ? !).
|
||||
|
||||
After that it is verified that `vote` has been signed by `vote.voter`. If not function goes to another vote in array (IDEA: maybe vote verification failed should be emitted ?).
|
||||
Then it is verified that the proposal with given `proposalId` exists and isn't closed, otherwise, function reverts.
|
||||
|
||||
Then it is checked that `vote.voter` didn't vote in this vote room before if he did function goes to another voter (IDEA: emit alreadyVoted ?).
|
||||
|
||||
After that it is verified that `vote` has been signed by `vote.voter`.
|
||||
|
||||
Last check is whether `vote.voter` has enough tokens to vote. If he does not `NotEnoughToken` is emitted and function goes to another voter. If he does voting room is updated with `updateRoomVotes` and `VoteCast` is emitted.
|
||||
Last check is whether `vote.voter` has enough tokens to vote.
|
||||
|
||||
If all checks passed, the vote is cast with `castVote` and `VoteCast` is emitted.
|
||||
|
@ -10,17 +10,18 @@ contract VotingContract {
|
||||
using SafeMath for uint256;
|
||||
|
||||
IERC20 public token;
|
||||
uint256 private votingLength;
|
||||
uint256 private voteDuration;
|
||||
|
||||
bytes32 private constant EIP712DOMAIN_TYPEHASH =
|
||||
keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)');
|
||||
bytes32 private constant VOTE_TYPEHASH = keccak256('Vote(uint256 roomIdAndType,uint256 tokenAmount,address voter)');
|
||||
bytes32 private constant VOTE_TYPEHASH =
|
||||
keccak256('Vote(uint256 proposalIdAndType,uint256 tokenAmount,address voter)');
|
||||
bytes32 private DOMAIN_SEPARATOR;
|
||||
|
||||
struct VotingRoom {
|
||||
struct Proposal {
|
||||
uint256 startBlock;
|
||||
uint256 endAt;
|
||||
string question;
|
||||
string title;
|
||||
string description;
|
||||
uint256 totalVotesFor;
|
||||
uint256 totalVotesAgainst;
|
||||
@ -28,22 +29,22 @@ contract VotingContract {
|
||||
address[] voters;
|
||||
}
|
||||
mapping(uint256 => mapping(address => bool)) private voted;
|
||||
VotingRoom[] public votingRooms;
|
||||
Proposal[] public proposals;
|
||||
|
||||
struct Vote {
|
||||
address voter;
|
||||
uint256 roomIdAndType;
|
||||
uint256 proposalIdAndType;
|
||||
uint256 tokenAmount;
|
||||
bytes32 r;
|
||||
bytes32 vs;
|
||||
}
|
||||
|
||||
event VoteCast(uint256 roomId, address voter);
|
||||
event VotingRoomStarted(uint256 roomId, string question);
|
||||
event VoteCast(uint256 proposalId, address voter);
|
||||
event ProposalStarted(uint256 proposalId, string title);
|
||||
|
||||
constructor(IERC20 _address, uint256 _votingLength) {
|
||||
constructor(IERC20 _address, uint256 _voteDuration) {
|
||||
token = _address;
|
||||
votingLength = _votingLength;
|
||||
voteDuration = _voteDuration;
|
||||
DOMAIN_SEPARATOR = keccak256(
|
||||
abi.encode(
|
||||
EIP712DOMAIN_TYPEHASH,
|
||||
@ -55,26 +56,26 @@ contract VotingContract {
|
||||
);
|
||||
}
|
||||
|
||||
function getVotingRoomLength() public view returns (uint256) {
|
||||
return votingRooms.length;
|
||||
function getProposalsLength() public view returns (uint256) {
|
||||
return proposals.length;
|
||||
}
|
||||
|
||||
function getLastNVotingRooms(uint256 amount) public view returns (VotingRoom[] memory result) {
|
||||
function getLastNProposals(uint256 amount) public view returns (Proposal[] memory result) {
|
||||
if (amount == 0) {
|
||||
return new VotingRoom[](0);
|
||||
return new Proposal[](0);
|
||||
}
|
||||
uint256 votingRoomsLen = votingRooms.length;
|
||||
uint256 proposalsLen = proposals.length;
|
||||
uint256 limit;
|
||||
if (amount > votingRoomsLen) {
|
||||
if (amount > proposalsLen) {
|
||||
limit = 0;
|
||||
} else {
|
||||
limit = votingRoomsLen - amount;
|
||||
limit = proposalsLen - amount;
|
||||
}
|
||||
uint256 i = votingRoomsLen;
|
||||
uint256 i = proposalsLen;
|
||||
uint256 j = 0;
|
||||
result = new VotingRoom[](amount);
|
||||
result = new Proposal[](amount);
|
||||
while (i > 0 && i > limit) {
|
||||
result[j++] = votingRooms[--i];
|
||||
result[j++] = proposals[--i];
|
||||
}
|
||||
if (j < amount) {
|
||||
assembly {
|
||||
@ -83,60 +84,63 @@ contract VotingContract {
|
||||
}
|
||||
}
|
||||
|
||||
function getVotingRoomsFrom(uint256 id) public view returns (VotingRoom[] memory result) {
|
||||
if (id + 1 > votingRooms.length) {
|
||||
return new VotingRoom[](0);
|
||||
function getProposalFromId(uint256 id) public view returns (Proposal[] memory result) {
|
||||
if (id + 1 > proposals.length) {
|
||||
return new Proposal[](0);
|
||||
}
|
||||
result = new VotingRoom[](votingRooms.length - id);
|
||||
result = new Proposal[](proposals.length - id);
|
||||
uint256 i = id;
|
||||
uint256 j = 0;
|
||||
while (i < votingRooms.length) {
|
||||
result[j++] = votingRooms[i++];
|
||||
while (i < proposals.length) {
|
||||
result[j++] = proposals[i++];
|
||||
}
|
||||
}
|
||||
|
||||
function getVotingRooms() public view returns (VotingRoom[] memory) {
|
||||
return votingRooms;
|
||||
function getProposals() public view returns (Proposal[] memory) {
|
||||
return proposals;
|
||||
}
|
||||
|
||||
function getOngoingVotingRooms() public view returns (VotingRoom[] memory result) {
|
||||
uint256 votingRoomsLen = votingRooms.length;
|
||||
uint256 i = votingRoomsLen;
|
||||
result = new VotingRoom[](votingRoomsLen);
|
||||
while (i > 0 && votingRooms[--i].endAt > block.timestamp) {
|
||||
result[votingRooms.length - 1 - i] = votingRooms[i];
|
||||
votingRoomsLen--;
|
||||
function getOpenProposals() public view returns (Proposal[] memory result) {
|
||||
uint256 proposalsLen = proposals.length;
|
||||
uint256 i = proposalsLen;
|
||||
result = new Proposal[](proposalsLen);
|
||||
while (i > 0 && proposals[--i].endAt > block.timestamp) {
|
||||
result[proposals.length - 1 - i] = proposals[i];
|
||||
proposalsLen--;
|
||||
}
|
||||
assembly {
|
||||
mstore(result, sub(mload(result), votingRoomsLen))
|
||||
mstore(result, sub(mload(result), proposalsLen))
|
||||
}
|
||||
}
|
||||
|
||||
function listRoomVoters(uint256 roomId) public view returns (address[] memory) {
|
||||
require(roomId < votingRooms.length, 'No room');
|
||||
return votingRooms[roomId].voters;
|
||||
function listVotersForProposal(uint256 proposalId) public view returns (address[] memory) {
|
||||
require(proposalId < proposals.length, 'No proposal for this id');
|
||||
return proposals[proposalId].voters;
|
||||
}
|
||||
|
||||
function initializeVotingRoom(
|
||||
string calldata question,
|
||||
function initializeProposal(
|
||||
string calldata title,
|
||||
string calldata description,
|
||||
uint256 voteAmount
|
||||
uint256 creatorVoteForAmount
|
||||
) public {
|
||||
require(voteAmount > 0, 'token amount must not be 0');
|
||||
require(token.balanceOf(msg.sender) >= voteAmount, 'not enough token');
|
||||
VotingRoom memory newVotingRoom;
|
||||
newVotingRoom.startBlock = block.number;
|
||||
newVotingRoom.endAt = block.timestamp.add(votingLength);
|
||||
newVotingRoom.question = question;
|
||||
newVotingRoom.description = description;
|
||||
newVotingRoom.totalVotesFor = voteAmount;
|
||||
newVotingRoom.id = votingRooms.length;
|
||||
voted[votingRooms.length][msg.sender] = true;
|
||||
require(creatorVoteForAmount > 0, 'token amount must not be 0');
|
||||
require(
|
||||
token.balanceOf(msg.sender) >= creatorVoteForAmount,
|
||||
'sender does not have the amount of token they voted for'
|
||||
);
|
||||
Proposal memory proposal;
|
||||
proposal.startBlock = block.number;
|
||||
proposal.endAt = block.timestamp.add(voteDuration);
|
||||
proposal.title = title;
|
||||
proposal.description = description;
|
||||
proposal.totalVotesFor = creatorVoteForAmount;
|
||||
proposal.id = proposals.length;
|
||||
voted[proposals.length][msg.sender] = true;
|
||||
|
||||
votingRooms.push(newVotingRoom);
|
||||
votingRooms[votingRooms.length - 1].voters.push(msg.sender);
|
||||
proposals.push(proposal);
|
||||
proposals[proposals.length - 1].voters.push(msg.sender);
|
||||
|
||||
emit VotingRoomStarted(votingRooms.length - 1, question);
|
||||
emit ProposalStarted(proposals.length - 1, title);
|
||||
}
|
||||
|
||||
function verify(
|
||||
@ -144,32 +148,32 @@ contract VotingContract {
|
||||
bytes32 r,
|
||||
bytes32 vs
|
||||
) internal view returns (bool) {
|
||||
bytes32 voteHash = keccak256(abi.encode(VOTE_TYPEHASH, vote.roomIdAndType, vote.tokenAmount, vote.voter));
|
||||
bytes32 voteHash = keccak256(abi.encode(VOTE_TYPEHASH, vote.proposalIdAndType, vote.tokenAmount, vote.voter));
|
||||
bytes32 digest = keccak256(abi.encodePacked('\x19\x01', DOMAIN_SEPARATOR, voteHash));
|
||||
return ECDSA.recover(digest, r, vs) == vote.voter;
|
||||
}
|
||||
|
||||
function updateRoomVotes(Vote calldata vote, uint256 roomId) internal {
|
||||
if (vote.roomIdAndType & 1 == 1) {
|
||||
votingRooms[roomId].totalVotesFor = votingRooms[roomId].totalVotesFor.add(vote.tokenAmount);
|
||||
function castVote(Vote calldata vote, uint256 proposalId) internal {
|
||||
if (vote.proposalIdAndType & 1 == 1) {
|
||||
proposals[proposalId].totalVotesFor = proposals[proposalId].totalVotesFor.add(vote.tokenAmount);
|
||||
} else {
|
||||
votingRooms[roomId].totalVotesAgainst = votingRooms[roomId].totalVotesAgainst.add(vote.tokenAmount);
|
||||
proposals[proposalId].totalVotesAgainst = proposals[proposalId].totalVotesAgainst.add(vote.tokenAmount);
|
||||
}
|
||||
votingRooms[roomId].voters.push(vote.voter);
|
||||
voted[roomId][vote.voter] = true;
|
||||
proposals[proposalId].voters.push(vote.voter);
|
||||
voted[proposalId][vote.voter] = true;
|
||||
}
|
||||
|
||||
function castVotes(Vote[] calldata votes) public {
|
||||
for (uint256 i = 0; i < votes.length; i++) {
|
||||
Vote calldata vote = votes[i];
|
||||
uint256 roomId = vote.roomIdAndType >> 1;
|
||||
require(roomId < votingRooms.length, 'vote not found');
|
||||
require(votingRooms[roomId].endAt > block.timestamp, 'vote closed');
|
||||
uint256 proposalId = vote.proposalIdAndType >> 1;
|
||||
require(proposalId < proposals.length, 'proposal does not exist');
|
||||
require(proposals[proposalId].endAt > block.timestamp, 'vote closed for this proposal');
|
||||
require(voted[proposalId][vote.voter] == false, 'voter already voted');
|
||||
require(verify(vote, vote.r, vote.vs), 'vote has wrong signature');
|
||||
require(voted[roomId][vote.voter] == false, 'voter already voted');
|
||||
require(token.balanceOf(vote.voter) >= vote.tokenAmount, 'voter doesnt have enough tokens');
|
||||
updateRoomVotes(vote, roomId);
|
||||
emit VoteCast(roomId, vote.voter);
|
||||
castVote(vote, proposalId);
|
||||
emit VoteCast(proposalId, vote.voter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const typedData = {
|
||||
{ name: 'verifyingContract', type: 'address' },
|
||||
],
|
||||
Vote: [
|
||||
{ name: 'roomIdAndType', type: 'uint256' },
|
||||
{ name: 'proposalIdAndType', type: 'uint256' },
|
||||
{ name: 'tokenAmount', type: 'uint256' },
|
||||
{ name: 'voter', type: 'address' },
|
||||
],
|
||||
@ -69,7 +69,7 @@ const getSignedMessages = async (
|
||||
const signedMessages = messages.map((msg, idx) => {
|
||||
const t: TypedMessage<MessageTypes> = {
|
||||
...typedData,
|
||||
message: { roomIdAndType: msg[1].toHexString(), tokenAmount: msg[2].toHexString(), voter: msg[0] },
|
||||
message: { proposalIdAndType: msg[1].toHexString(), tokenAmount: msg[2].toHexString(), voter: msg[0] },
|
||||
}
|
||||
const sig = utils.splitSignature(
|
||||
signTypedMessage(Buffer.from(utils.arrayify(votes[idx].voter.privateKey)), { data: t }, 'V3')
|
||||
@ -97,38 +97,38 @@ before(async function () {
|
||||
})
|
||||
|
||||
describe('Contract', () => {
|
||||
describe('Voting Room', () => {
|
||||
describe('Proposal', () => {
|
||||
describe('initialization', () => {
|
||||
it('initializes', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
|
||||
await expect(await contract.initializeVotingRoom('test', 'short desc', BigNumber.from(100)))
|
||||
.to.emit(contract, 'VotingRoomStarted')
|
||||
await expect(await contract.initializeProposal('test', 'short desc', BigNumber.from(100)))
|
||||
.to.emit(contract, 'ProposalStarted')
|
||||
.withArgs(0, 'test')
|
||||
await expect(await contract.initializeVotingRoom('test2', 'short desc', BigNumber.from(100)))
|
||||
.to.emit(contract, 'VotingRoomStarted')
|
||||
await expect(await contract.initializeProposal('test2', 'short desc', BigNumber.from(100)))
|
||||
.to.emit(contract, 'ProposalStarted')
|
||||
.withArgs(1, 'test2')
|
||||
})
|
||||
|
||||
it('not enough token', async () => {
|
||||
it('sender does not have the amount of token they voted for', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await expect(
|
||||
contract.initializeVotingRoom('test', 'short desc', BigNumber.from(10000000000000))
|
||||
).to.be.revertedWith('not enough token')
|
||||
contract.initializeProposal('test', 'short desc', BigNumber.from(10000000000000))
|
||||
).to.be.revertedWith('sender does not have the amount of token they voted for')
|
||||
})
|
||||
|
||||
it('no tokens address', async () => {
|
||||
const { contract, noTokensAddress } = await loadFixture(fixture)
|
||||
const noTokensContract = contract.connect(noTokensAddress)
|
||||
await expect(
|
||||
noTokensContract.initializeVotingRoom('test', 'short desc', BigNumber.from(10))
|
||||
).to.be.revertedWith('not enough token')
|
||||
await expect(noTokensContract.initializeProposal('test', 'short desc', BigNumber.from(10))).to.be.revertedWith(
|
||||
'sender does not have the amount of token they voted for'
|
||||
)
|
||||
})
|
||||
|
||||
it("can't start voting with 0 tokens", async () => {
|
||||
const { contract, noTokensAddress } = await loadFixture(fixture)
|
||||
const noTokensContract = contract.connect(noTokensAddress)
|
||||
await expect(noTokensContract.initializeVotingRoom('test', 'short desc', BigNumber.from(0))).to.be.revertedWith(
|
||||
await expect(noTokensContract.initializeProposal('test', 'short desc', BigNumber.from(0))).to.be.revertedWith(
|
||||
'token amount must not be 0'
|
||||
)
|
||||
})
|
||||
@ -136,204 +136,204 @@ describe('Contract', () => {
|
||||
|
||||
it('gets', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 'short desc', BigNumber.from(100))
|
||||
const votingRoom0 = await contract.votingRooms(0)
|
||||
expect(votingRoom0[2]).to.eq('T1')
|
||||
expect(votingRoom0[3]).to.eq('short desc')
|
||||
expect(votingRoom0[4]).to.deep.eq(BigNumber.from(100))
|
||||
expect(votingRoom0[5]).to.deep.eq(BigNumber.from(0))
|
||||
expect(votingRoom0[6]).to.deep.eq(BigNumber.from(0))
|
||||
await contract.initializeProposal('T1', 'short desc', BigNumber.from(100))
|
||||
const proposals = await contract.proposals(0)
|
||||
expect(proposals[2]).to.eq('T1')
|
||||
expect(proposals[3]).to.eq('short desc')
|
||||
expect(proposals[4]).to.deep.eq(BigNumber.from(100))
|
||||
expect(proposals[5]).to.deep.eq(BigNumber.from(0))
|
||||
expect(proposals[6]).to.deep.eq(BigNumber.from(0))
|
||||
|
||||
await contract.initializeVotingRoom('T2', 'long desc', BigNumber.from(200))
|
||||
expect((await contract.votingRooms(1))[2]).to.eq('T2')
|
||||
expect((await contract.votingRooms(1))[3]).to.eq('long desc')
|
||||
expect((await contract.votingRooms(1))[4]).to.deep.eq(BigNumber.from(200))
|
||||
expect((await contract.votingRooms(1))[5]).to.deep.eq(BigNumber.from(0))
|
||||
expect((await contract.votingRooms(1))[6]).to.deep.eq(BigNumber.from(1))
|
||||
await contract.initializeProposal('T2', 'long desc', BigNumber.from(200))
|
||||
expect((await contract.proposals(1))[2]).to.eq('T2')
|
||||
expect((await contract.proposals(1))[3]).to.eq('long desc')
|
||||
expect((await contract.proposals(1))[4]).to.deep.eq(BigNumber.from(200))
|
||||
expect((await contract.proposals(1))[5]).to.deep.eq(BigNumber.from(0))
|
||||
expect((await contract.proposals(1))[6]).to.deep.eq(BigNumber.from(1))
|
||||
})
|
||||
|
||||
it('reverts no room', async () => {
|
||||
it('reverts: no proposal', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await expect(contract.votingRooms(1)).to.be.reverted
|
||||
await expect(contract.votingRooms(0)).to.be.reverted
|
||||
await contract.initializeVotingRoom('T2', '', BigNumber.from(200))
|
||||
await expect(contract.votingRooms(1)).to.be.reverted
|
||||
await expect(contract.proposals(1)).to.be.reverted
|
||||
await expect(contract.proposals(0)).to.be.reverted
|
||||
await contract.initializeProposal('T2', '', BigNumber.from(200))
|
||||
await expect(contract.proposals(1)).to.be.reverted
|
||||
})
|
||||
|
||||
it('getOngoingVotingRooms', async () => {
|
||||
it('getOpenProposals', async () => {
|
||||
const { contract, provider } = await loadFixture(fixture)
|
||||
|
||||
await expect((await contract.getOngoingVotingRooms()).length).to.eq(0)
|
||||
await contract.initializeVotingRoom('test1', 'short desc', BigNumber.from(100))
|
||||
await expect((await contract.getOpenProposals()).length).to.eq(0)
|
||||
await contract.initializeProposal('test1', 'short desc', BigNumber.from(100))
|
||||
|
||||
let rooms
|
||||
rooms = await contract.getOngoingVotingRooms()
|
||||
await expect(rooms.length).to.eq(1)
|
||||
await expect(rooms[0][2]).to.eq('test1')
|
||||
let proposals
|
||||
proposals = await contract.getOpenProposals()
|
||||
await expect(proposals.length).to.eq(1)
|
||||
await expect(proposals[0][2]).to.eq('test1')
|
||||
await provider.send('evm_increaseTime', [500])
|
||||
await provider.send('evm_mine', [])
|
||||
|
||||
await contract.initializeVotingRoom('test2', 'short desc', BigNumber.from(100))
|
||||
rooms = await contract.getOngoingVotingRooms()
|
||||
await expect(rooms.length).to.eq(2)
|
||||
await expect(rooms[0][2]).to.eq('test2')
|
||||
await expect(rooms[1][2]).to.eq('test1')
|
||||
await contract.initializeProposal('test2', 'short desc', BigNumber.from(100))
|
||||
proposals = await contract.getOpenProposals()
|
||||
await expect(proposals.length).to.eq(2)
|
||||
await expect(proposals[0][2]).to.eq('test2')
|
||||
await expect(proposals[1][2]).to.eq('test1')
|
||||
await provider.send('evm_increaseTime', [600])
|
||||
await provider.send('evm_mine', [])
|
||||
|
||||
rooms = await contract.getOngoingVotingRooms()
|
||||
proposals = await contract.getOpenProposals()
|
||||
|
||||
await expect(rooms.length).to.eq(1)
|
||||
await expect(rooms[0][2]).to.eq('test2')
|
||||
await expect(proposals.length).to.eq(1)
|
||||
await expect(proposals[0][2]).to.eq('test2')
|
||||
await provider.send('evm_increaseTime', [600])
|
||||
await provider.send('evm_mine', [])
|
||||
rooms = await contract.getOngoingVotingRooms()
|
||||
await expect(rooms.length).to.eq(0)
|
||||
proposals = await contract.getOpenProposals()
|
||||
await expect(proposals.length).to.eq(0)
|
||||
})
|
||||
})
|
||||
describe('helpers', () => {
|
||||
describe('getLastNVotingRooms', () => {
|
||||
describe('getLastNProposals', () => {
|
||||
it('get 0 voting empty', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
expect((await contract.getLastNVotingRooms(0)).length).to.eq(0)
|
||||
expect((await contract.getLastNProposals(0)).length).to.eq(0)
|
||||
})
|
||||
|
||||
it('get 1 voting empty', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
expect((await contract.getLastNVotingRooms(1)).length).to.eq(0)
|
||||
expect((await contract.getLastNProposals(1)).length).to.eq(0)
|
||||
})
|
||||
|
||||
it('get 1 voting 1', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getLastNVotingRooms(1)).length).to.eq(1)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getLastNProposals(1)).length).to.eq(1)
|
||||
})
|
||||
|
||||
it('get 5 voting empty', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(0)
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(0)
|
||||
})
|
||||
|
||||
it('get 5 voting 1', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(1)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(1)
|
||||
})
|
||||
|
||||
it('get 5 voting 2', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(2)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(2)
|
||||
})
|
||||
|
||||
it('get 5 voting 4', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T4', 't4', BigNumber.from(200))
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(4)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeProposal('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeProposal('T4', 't4', BigNumber.from(200))
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(4)
|
||||
})
|
||||
|
||||
it('get 5 voting 5', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T4', 't4', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T5', 't5', BigNumber.from(200))
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(5)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeProposal('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeProposal('T4', 't4', BigNumber.from(200))
|
||||
await contract.initializeProposal('T5', 't5', BigNumber.from(200))
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(5)
|
||||
})
|
||||
|
||||
it('get 5 voting 6', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T4', 't4', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T5', 't5', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T6', 't6', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T7', 't7', BigNumber.from(200))
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(5)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeProposal('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeProposal('T4', 't4', BigNumber.from(200))
|
||||
await contract.initializeProposal('T5', 't5', BigNumber.from(200))
|
||||
await contract.initializeProposal('T6', 't6', BigNumber.from(200))
|
||||
await contract.initializeProposal('T7', 't7', BigNumber.from(200))
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(5)
|
||||
})
|
||||
|
||||
it('get 5 voting 7', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T4', 't4', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T5', 't5', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T6', 't6', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T7', 't7', BigNumber.from(200))
|
||||
expect((await contract.getLastNVotingRooms(5)).length).to.eq(5)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeProposal('T3', 't3', BigNumber.from(200))
|
||||
await contract.initializeProposal('T4', 't4', BigNumber.from(200))
|
||||
await contract.initializeProposal('T5', 't5', BigNumber.from(200))
|
||||
await contract.initializeProposal('T6', 't6', BigNumber.from(200))
|
||||
await contract.initializeProposal('T7', 't7', BigNumber.from(200))
|
||||
expect((await contract.getLastNProposals(5)).length).to.eq(5)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getVotingRoomsFrom', () => {
|
||||
describe('getProposalFromId', () => {
|
||||
it('empty', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
expect((await contract.getVotingRoomsFrom(1)).length).to.eq(0)
|
||||
expect((await contract.getProposalFromId(1)).length).to.eq(0)
|
||||
})
|
||||
it('from 1 votingRooms 1', async () => {
|
||||
it('from 1 proposal 1', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getVotingRoomsFrom(1)).length).to.eq(0)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getProposalFromId(1)).length).to.eq(0)
|
||||
})
|
||||
|
||||
it('from 1 votingRooms 2', async () => {
|
||||
it('from 1 proposal 2', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
expect((await contract.getVotingRoomsFrom(1)).length).to.eq(1)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
expect((await contract.getProposalFromId(1)).length).to.eq(1)
|
||||
})
|
||||
|
||||
it('from 1 votingRooms 3', async () => {
|
||||
it('from 1 proposal 3', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeVotingRoom('T3', 't3', BigNumber.from(200))
|
||||
expect((await contract.getVotingRoomsFrom(1)).length).to.eq(2)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
await contract.initializeProposal('T3', 't3', BigNumber.from(200))
|
||||
expect((await contract.getProposalFromId(1)).length).to.eq(2)
|
||||
})
|
||||
|
||||
it('from 0 votingRooms 0', async () => {
|
||||
it('from 0 proposal 0', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
expect((await contract.getVotingRoomsFrom(0)).length).to.eq(0)
|
||||
expect((await contract.getProposalFromId(0)).length).to.eq(0)
|
||||
})
|
||||
|
||||
it('from 0 votingRooms 1', async () => {
|
||||
it('from 0 proposal 1', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getVotingRoomsFrom(0)).length).to.eq(1)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
expect((await contract.getProposalFromId(0)).length).to.eq(1)
|
||||
})
|
||||
|
||||
it('from 0 votingRooms 2', async () => {
|
||||
it('from 0 proposal 2', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(100))
|
||||
expect((await contract.getVotingRoomsFrom(0)).length).to.eq(2)
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(100))
|
||||
expect((await contract.getProposalFromId(0)).length).to.eq(2)
|
||||
})
|
||||
})
|
||||
|
||||
it('get voting rooms', async () => {
|
||||
it('get proposals', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('T1', 't1', BigNumber.from(100))
|
||||
await contract.initializeProposal('T1', 't1', BigNumber.from(100))
|
||||
|
||||
await contract.initializeVotingRoom('T2', 't2', BigNumber.from(200))
|
||||
const votingRooms = await contract.getVotingRooms()
|
||||
await contract.initializeProposal('T2', 't2', BigNumber.from(200))
|
||||
const proposals = await contract.getProposals()
|
||||
|
||||
expect(votingRooms.length).to.eq(2)
|
||||
expect(proposals.length).to.eq(2)
|
||||
|
||||
expect(votingRooms[0][2]).to.eq('T1')
|
||||
expect(votingRooms[0][3]).to.eq('t1')
|
||||
expect(votingRooms[0][4]).to.deep.eq(BigNumber.from(100))
|
||||
expect(votingRooms[0][5]).to.deep.eq(BigNumber.from(0))
|
||||
expect(proposals[0][2]).to.eq('T1')
|
||||
expect(proposals[0][3]).to.eq('t1')
|
||||
expect(proposals[0][4]).to.deep.eq(BigNumber.from(100))
|
||||
expect(proposals[0][5]).to.deep.eq(BigNumber.from(0))
|
||||
|
||||
expect(votingRooms[1][2]).to.eq('T2')
|
||||
expect(votingRooms[1][3]).to.eq('t2')
|
||||
expect(votingRooms[1][4]).to.deep.eq(BigNumber.from(200))
|
||||
expect(votingRooms[1][5]).to.deep.eq(BigNumber.from(0))
|
||||
expect(proposals[1][2]).to.eq('T2')
|
||||
expect(proposals[1][3]).to.eq('t2')
|
||||
expect(proposals[1][4]).to.deep.eq(BigNumber.from(200))
|
||||
expect(proposals[1][5]).to.deep.eq(BigNumber.from(0))
|
||||
})
|
||||
})
|
||||
|
||||
@ -341,22 +341,22 @@ describe('Contract', () => {
|
||||
it('check voters', async () => {
|
||||
const { contract, alice, firstAddress, secondAddress } = await loadFixture(fixture)
|
||||
const { signedMessages } = await getSignedMessages(alice, firstAddress, secondAddress)
|
||||
await contract.initializeVotingRoom('0xabA1eF51ef4aE360a9e8C9aD2d787330B602eb24', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('0xabA1eF51ef4aE360a9e8C9aD2d787330B602eb24', '', BigNumber.from(100))
|
||||
|
||||
expect(await contract.listRoomVoters(0)).to.deep.eq([alice.address])
|
||||
expect(await contract.listVotersForProposal(0)).to.deep.eq([alice.address])
|
||||
await contract.castVotes(signedMessages.slice(1))
|
||||
|
||||
expect(await contract.listRoomVoters(0)).to.deep.eq([alice.address, secondAddress.address])
|
||||
expect(await contract.listVotersForProposal(0)).to.deep.eq([alice.address, secondAddress.address])
|
||||
})
|
||||
|
||||
it('not enough tokens', async () => {
|
||||
it('voter doesnt have enough tokens', async () => {
|
||||
const { contract, firstAddress } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', '', BigNumber.from(100))
|
||||
|
||||
const msg = [firstAddress.address, BigNumber.from(0).mul(2).add(1), BigNumber.from(100000000000)]
|
||||
const t: TypedMessage<MessageTypes> = {
|
||||
...typedData,
|
||||
message: { roomIdAndType: msg[1].toHexString(), tokenAmount: msg[2].toHexString(), voter: msg[0] },
|
||||
message: { proposalIdAndType: msg[1].toHexString(), tokenAmount: msg[2].toHexString(), voter: msg[0] },
|
||||
}
|
||||
const sig = utils.splitSignature(
|
||||
signTypedMessage(Buffer.from(utils.arrayify(firstAddress.privateKey)), { data: t }, 'V3')
|
||||
@ -365,71 +365,71 @@ describe('Contract', () => {
|
||||
|
||||
await expect(contract.castVotes([signedMessage])).to.be.revertedWith('voter doesnt have enough tokens')
|
||||
|
||||
const votingRoom = await contract.votingRooms(0)
|
||||
expect(votingRoom[2]).to.eq('test')
|
||||
expect(votingRoom[3]).to.eq('')
|
||||
expect(votingRoom[4]).to.deep.eq(BigNumber.from(100))
|
||||
expect(votingRoom[5]).to.deep.eq(BigNumber.from(0))
|
||||
const proposals = await contract.proposals(0)
|
||||
expect(proposals[2]).to.eq('test')
|
||||
expect(proposals[3]).to.eq('')
|
||||
expect(proposals[4]).to.deep.eq(BigNumber.from(100))
|
||||
expect(proposals[5]).to.deep.eq(BigNumber.from(0))
|
||||
})
|
||||
|
||||
it('success', async () => {
|
||||
const { contract, alice, firstAddress, secondAddress } = await loadFixture(fixture)
|
||||
const { signedMessages } = await getSignedMessages(alice, firstAddress, secondAddress)
|
||||
await contract.initializeVotingRoom('test', 'test desc', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', 'test desc', BigNumber.from(100))
|
||||
await contract.castVotes(signedMessages)
|
||||
|
||||
const votingRoom = await contract.votingRooms(0)
|
||||
expect(votingRoom[2]).to.eq('test')
|
||||
expect(votingRoom[3]).to.eq('test desc')
|
||||
expect(votingRoom[4]).to.deep.eq(BigNumber.from(200))
|
||||
expect(votingRoom[5]).to.deep.eq(BigNumber.from(100))
|
||||
const proposals = await contract.proposals(0)
|
||||
expect(proposals[2]).to.eq('test')
|
||||
expect(proposals[3]).to.eq('test desc')
|
||||
expect(proposals[4]).to.deep.eq(BigNumber.from(200))
|
||||
expect(proposals[5]).to.deep.eq(BigNumber.from(100))
|
||||
})
|
||||
|
||||
it('double vote', async () => {
|
||||
const { contract, alice, firstAddress, secondAddress } = await loadFixture(fixture)
|
||||
const { signedMessages } = await getSignedMessages(alice, firstAddress, secondAddress)
|
||||
await contract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', '', BigNumber.from(100))
|
||||
await contract.castVotes(signedMessages)
|
||||
await expect(contract.castVotes(signedMessages)).to.be.revertedWith('voter already voted')
|
||||
|
||||
const votingRoom = await contract.votingRooms(0)
|
||||
expect(votingRoom[2]).to.eq('test')
|
||||
expect(votingRoom[3]).to.eq('')
|
||||
expect(votingRoom[4]).to.deep.eq(BigNumber.from(200))
|
||||
expect(votingRoom[5]).to.deep.eq(BigNumber.from(100))
|
||||
const proposals = await contract.proposals(0)
|
||||
expect(proposals[2]).to.eq('test')
|
||||
expect(proposals[3]).to.eq('')
|
||||
expect(proposals[4]).to.deep.eq(BigNumber.from(200))
|
||||
expect(proposals[5]).to.deep.eq(BigNumber.from(100))
|
||||
})
|
||||
|
||||
it('random bytes', async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
await contract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', '', BigNumber.from(100))
|
||||
await expect(contract.castVotes([new Uint8Array([12, 12, 12])])).to.be.reverted
|
||||
})
|
||||
|
||||
it('none existent room', async () => {
|
||||
it('none existent proposal', async () => {
|
||||
const { contract, alice, firstAddress, secondAddress } = await loadFixture(fixture)
|
||||
const { signedMessages } = await getSignedMessages(alice, firstAddress, secondAddress)
|
||||
await expect(contract.castVotes(signedMessages)).to.be.revertedWith('vote not found')
|
||||
await expect(contract.castVotes(signedMessages)).to.be.revertedWith('proposal does not exist')
|
||||
})
|
||||
|
||||
it('old room', async () => {
|
||||
it('proposal closed', async () => {
|
||||
const { contract, alice, firstAddress, secondAddress, provider } = await loadFixture(fixture)
|
||||
const { signedMessages } = await getSignedMessages(alice, firstAddress, secondAddress)
|
||||
await contract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', '', BigNumber.from(100))
|
||||
await provider.send('evm_increaseTime', [10000])
|
||||
await expect(contract.castVotes(signedMessages)).to.be.revertedWith('vote closed')
|
||||
await expect(contract.castVotes(signedMessages)).to.be.revertedWith('vote closed for this proposal')
|
||||
})
|
||||
|
||||
it('wrong signature', async () => {
|
||||
const { contract, alice, firstAddress, secondAddress } = await loadFixture(fixture)
|
||||
const { messages } = await getSignedMessages(alice, firstAddress, secondAddress)
|
||||
|
||||
await contract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', '', BigNumber.from(100))
|
||||
|
||||
const signedMessages = await Promise.all(
|
||||
messages.map(async (msg) => {
|
||||
const t: TypedMessage<MessageTypes> = {
|
||||
...typedData,
|
||||
message: { roomIdAndType: msg[1].toHexString(), tokenAmount: msg[2].toHexString(), voter: msg[0] },
|
||||
message: { proposalIdAndType: msg[1].toHexString(), tokenAmount: msg[2].toHexString(), voter: msg[0] },
|
||||
}
|
||||
const sig = utils.splitSignature(
|
||||
signTypedMessage(Buffer.from(utils.arrayify(firstAddress.privateKey)), { data: t }, 'V3')
|
||||
@ -448,7 +448,7 @@ describe('Contract', () => {
|
||||
const correctMessageData: TypedMessage<MessageTypes> = {
|
||||
...domainSeparator,
|
||||
message: {
|
||||
roomIdAndType: correctMessageParams[1].toHexString(),
|
||||
proposalIdAndType: correctMessageParams[1].toHexString(),
|
||||
tokenAmount: correctMessageParams[2].toHexString(),
|
||||
voter: correctMessageParams[0],
|
||||
},
|
||||
@ -463,8 +463,8 @@ describe('Contract', () => {
|
||||
|
||||
const { contract, secondContract, firstAddress } = await loadFixture(fixture)
|
||||
|
||||
await contract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await secondContract.initializeVotingRoom('test', '', BigNumber.from(100))
|
||||
await contract.initializeProposal('test', '', BigNumber.from(100))
|
||||
await secondContract.initializeProposal('test', '', BigNumber.from(100))
|
||||
|
||||
const correctAddress = createMessage(firstAddress, typedData)
|
||||
await expect(contract.castVotes([correctAddress])).not.to.be.reverted
|
||||
|
@ -18,7 +18,7 @@ message Vote {
|
||||
`)
|
||||
|
||||
type Message = {
|
||||
roomIdAndType: string
|
||||
proposalIdAndType: string
|
||||
tokenAmount: string
|
||||
voter: string
|
||||
}
|
||||
@ -43,7 +43,7 @@ export function createSignMsgParams(message: Message, chainId: number, verifying
|
||||
{ name: 'verifyingContract', type: 'address' },
|
||||
],
|
||||
Vote: [
|
||||
{ name: 'roomIdAndType', type: 'uint256' },
|
||||
{ name: 'proposalIdAndType', type: 'uint256' },
|
||||
{ name: 'tokenAmount', type: 'uint256' },
|
||||
{ name: 'voter', type: 'address' },
|
||||
],
|
||||
@ -92,7 +92,7 @@ export class VoteMsg {
|
||||
const signFunction = createSignFunction(signer)
|
||||
const voter = await signer.getAddress()
|
||||
const msg = {
|
||||
roomIdAndType: BigNumber.from(roomId).mul(2).add(answer).toHexString(),
|
||||
proposalIdAndType: BigNumber.from(roomId).mul(2).add(answer).toHexString(),
|
||||
tokenAmount: tokenAmount.toHexString(),
|
||||
voter,
|
||||
}
|
||||
@ -136,7 +136,7 @@ export class VoteMsg {
|
||||
const signature = utils.hexlify(payload.signature)
|
||||
|
||||
const msg = {
|
||||
roomIdAndType: BigNumber.from(payload.roomId).mul(2).add(payload.answer).toHexString(),
|
||||
proposalIdAndType: BigNumber.from(payload.roomId).mul(2).add(payload.answer).toHexString(),
|
||||
tokenAmount: utils.hexlify(payload.tokenAmount),
|
||||
voter: utils.getAddress(utils.hexlify(payload.voter)),
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user