-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Signature voting PR #58
base: signature-voting
Are you sure you want to change the base?
Changes from all commits
69c5c69
448b4be
72cab6c
aa58b44
1306c18
69e35b8
1fa51c7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,8 @@ | ||
# Template For Challenge - ETH Tech Tree | ||
*--Change the above "Template For Challenge" to the challenge name--* | ||
*--Add a paragraph sized story that pulls in the challenger to their mission--* | ||
# Signature Voting - ETH Tech Tree | ||
|
||
You're colonizing Mars and you have the opportunity to create a new society. Since it's known that you have some Solidity skills, the rest of your cohort asked you to code a 'trustless' voting system to make decisions about how this new world will be designed and governed. You need exactly one vote per wallet address per proposal. Imagine that each colonizer is issued exactly one wallet address so that there is no duplicate voting. | ||
|
||
*--End of story section--* | ||
Alice was working on some code for this voting system but didn't finish and decided to stay on Earth. Finish Alice's signature voting code. | ||
|
||
## Contents | ||
- [Requirements](#requirements) | ||
|
@@ -28,7 +27,7 @@ foundryup | |
|
||
## Challenge Description | ||
*--Edit this section--* | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove this line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And any other lines with |
||
Write challenge description here... | ||
Voters can sign messages off chain. The sender of a 'vote' transaction may not be the wallet address that signed the message. So, we need a way to get the signer of a signed message in order to record their vote for the correct proposal. | ||
|
||
Here are some helpful references: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make sure you remove these OR even better, add some references for the user to read. |
||
*Replace with real resource links if any* | ||
|
This file was deleted.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The contract structure should match this:
Let's also designate the different sections with large block comments like this:
Lastly, use custom errors. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0 <0.9.0; | ||
//import { console2 } from "forge-std/console2.sol"; | ||
|
||
contract SignatureVoting { | ||
|
||
struct Proposal { | ||
string name; | ||
uint256 voteCount; | ||
} | ||
|
||
// Create a way to track if someone has voted on a proposal already | ||
mapping(address => mapping(uint256 => bool)) internal voted; | ||
|
||
// A storage array of Proposal structs | ||
Proposal[] public proposals; | ||
|
||
// Creates a proposal | ||
function createProposal (string memory proposalName) external { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Every function should have NatSpec comments with a requirements section that tells the user what functionality is expected from the function. See the other completed challenges for an example. |
||
proposals.push(Proposal({ | ||
name: proposalName, | ||
voteCount: 0 | ||
})); | ||
} | ||
|
||
// Create a function to recover the signer from a signed message | ||
function recoverSigner( | ||
bytes32 _ethSignedMessageHash, | ||
bytes memory _signature | ||
) public pure returns (address) { | ||
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); | ||
|
||
return ecrecover(_ethSignedMessageHash, v, r, s); | ||
} | ||
|
||
// Split the signature into "r", "s", and "v" parameters for 'recoverSigner' | ||
function splitSignature(bytes memory sig) | ||
public | ||
pure | ||
returns (bytes32 r, bytes32 s, uint8 v) | ||
{ | ||
require(sig.length == 65, "invalid signature length"); | ||
|
||
assembly { | ||
/* | ||
First 32 bytes stores the length of the signature | ||
|
||
add(sig, 32) = pointer of sig + 32 | ||
effectively, skips first 32 bytes of signature | ||
|
||
mload(p) loads next 32 bytes starting at the memory address p into memory | ||
*/ | ||
|
||
// first 32 bytes, after the length prefix | ||
r := mload(add(sig, 32)) | ||
// second 32 bytes | ||
s := mload(add(sig, 64)) | ||
// final byte (first byte of the next 32 bytes) | ||
v := byte(0, mload(add(sig, 96))) | ||
} | ||
|
||
// implicitly return (r, s, v) | ||
} | ||
|
||
// Create a function to vote on a proposal | ||
function vote (bytes32 signedMessage, bytes32 hashedMessage, uint256 proposalId) public { | ||
// Get the address that signed the message | ||
// Not using msg.sender because the signer may not have sent the transaction | ||
address voter = recoverSigner(signedMessage, abi.encodePacked(hashedMessage)); | ||
|
||
// Prevent duplicate votes from voter | ||
// require(voted[voter][proposalId] == false, "Voter already voted for this proposal!"); | ||
|
||
// Verify hashed message is same as message | ||
//require(hashedMessage == keccak256(abi.encodePacked(proposalId)), "Vote: Messages don't match!"); | ||
|
||
// Increase by one vote for the proposal | ||
proposals[proposalId].voteCount += 1; | ||
|
||
// Record that voter has voted for proposal | ||
voted[voter][proposalId] == true; | ||
} | ||
|
||
// Query if voter voted on a proposal | ||
function queryVoted (address voter, uint256 proposalId) public view returns(bool) { | ||
return voted[voter][proposalId]; | ||
} | ||
|
||
// Create a function to get name of a proposal by proposalId | ||
function getProposalName (uint256 _proposalId) public view returns(string memory) { | ||
Proposal storage proposal = proposals[_proposalId]; | ||
return proposal.name; | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity ^0.8.13; | ||
|
||
import "forge-std/Test.sol"; | ||
import "../contracts/SignatureVoting.sol"; | ||
|
||
contract SignatureVotingTest is Test { | ||
// Declare variables | ||
SignatureVoting public signatureVoting; | ||
|
||
// Create users' privateKeys | ||
// Need privateKeys to create wallets and to 'sign' messages | ||
uint256 onePk = 0x123; | ||
uint256 twoPk = 0x456; | ||
uint256 threePk = 0x789; | ||
|
||
// Create users' wallets | ||
address public userOne = vm.addr(onePk); | ||
address public userTwo = vm.addr(twoPk); | ||
address public userThree = vm.addr(threePk); | ||
|
||
// Create proposals | ||
string public proposalOne = "Everyone must wear red"; | ||
string public proposalTwo = "Falafel Friday"; | ||
string public proposalThree = "Water is a public right"; | ||
|
||
// Create messages | ||
uint256 messageOne = 0; | ||
uint256 messageTwo = 1; | ||
uint256 messageThree = 2; | ||
|
||
// ToDo: Create messages | ||
bytes32 hashOne = keccak256(abi.encodePacked(messageOne)); | ||
bytes32 hashTwo = keccak256(abi.encodePacked(messageTwo)); | ||
bytes32 hashThree = keccak256(abi.encodePacked(messageThree)); | ||
|
||
// Deploy contract and create proposals | ||
function setUp() public { | ||
signatureVoting = new SignatureVoting(); | ||
signatureVoting.createProposal(proposalOne); | ||
signatureVoting.createProposal(proposalTwo); | ||
signatureVoting.createProposal(proposalThree); | ||
} | ||
|
||
// Test proposals were created | ||
function test_ProposalsCreated() public { | ||
assertEq(signatureVoting.getProposalName(0), proposalOne); | ||
assertEq(signatureVoting.getProposalName(1), proposalTwo); | ||
assertEq(signatureVoting.getProposalName(2), proposalThree); | ||
} | ||
|
||
// Voters sign message and call 'vote' | ||
function voterVotes() public { | ||
// Sign the message | ||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(onePk, hashOne); | ||
// Pack the message | ||
bytes32 signedMessage = keccak256(abi.encodePacked(r, s, v)); | ||
// Call smart contract | ||
signatureVoting.vote(signedMessage, hashOne, messageOne); | ||
} | ||
|
||
// Test that voters voted | ||
function test_voterVotedProposal0() public { | ||
assertTrue(signatureVoting.queryVoted(userOne, 0), "Voter did not vote for this proposal"); | ||
} | ||
|
||
// Test for duplicate votes for single proposal | ||
function test_DuplicateVoting() public { | ||
vm.expectRevert(bytes("Voter already voted for this proposal!")); | ||
signatureVoting.queryVoted(userOne, messageOne); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make sure you pull the latest from main branch. It will make some changes to the readme.