-
Notifications
You must be signed in to change notification settings - Fork 175
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
Add basic implementation to commit votes #393
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
pragma solidity ^0.5.0; | ||
|
||
|
||
/** | ||
* @title Voting system for Oracle. | ||
* @dev Handles receiving and resolving price requests via a commit-reveal voting scheme. | ||
*/ | ||
contract Voting { | ||
|
||
struct VoteInstance { | ||
// Maps (voterAddress) to their committed hash. | ||
// A bytes32 of `0` indicates no commit or a commit that was already revealed. | ||
mapping(address => bytes32) committedHashes; | ||
} | ||
|
||
// Conceptually maps (identifier, time) to a `VoteInstance`. | ||
mapping(bytes32 => mapping(uint => VoteInstance)) private requests; | ||
|
||
/** | ||
* @notice Commit your vote for a price request for `identifier` at `time`. | ||
* @dev (`identifier`, `time`) must correspond to a price request that's currently in the commit phase. `hash` | ||
* should be the keccak256 hash of the price you want to vote for and a `int salt`. Commits can be changed. | ||
*/ | ||
function commitVote(bytes32 identifier, uint time, bytes32 hash) external { | ||
require(hash != bytes32(0), "Committed hash of 0 is disallowed, choose a different salt"); | ||
VoteInstance storage voteInstance = requests[identifier][time]; | ||
voteInstance.committedHashes[msg.sender] = hash; | ||
} | ||
|
||
/** | ||
* @notice Reveal a previously committed vote for `identifier` at `time`. | ||
* @dev The revealed `price` and `salt` must match the latest `hash` that `commitVote()` was called with. Only the | ||
* committer can reveal their vote. | ||
*/ | ||
function revealVote(bytes32 identifier, uint time, int price, int salt) external { | ||
VoteInstance storage voteInstance = requests[identifier][time]; | ||
require(keccak256(abi.encode(price, salt)) == voteInstance.committedHashes[msg.sender], | ||
"Committed hash doesn't match revealed price and salt"); | ||
delete voteInstance.committedHashes[msg.sender]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
const { didContractThrow } = require("../../common/SolidityTestUtils.js"); | ||
|
||
const Voting = artifacts.require("Voting"); | ||
|
||
contract("Voting", function(accounts) { | ||
const account1 = accounts[0]; | ||
const account2 = accounts[1]; | ||
const account3 = accounts[2]; | ||
const account4 = accounts[3]; | ||
|
||
const getRandomInt = () => { | ||
return web3.utils.toBN(web3.utils.randomHex(32)); | ||
}; | ||
|
||
it("One voter, one request", async function() { | ||
const voting = await Voting.new(); | ||
|
||
const identifier = web3.utils.utf8ToHex("id"); | ||
const time = "1000"; | ||
|
||
const price = getRandomInt(); | ||
const salt = getRandomInt(); | ||
const hash = web3.utils.soliditySha3(price, salt); | ||
|
||
// Can't commit hash of 0. | ||
assert(await didContractThrow(voting.commitVote(identifier, time, "0x0"))); | ||
|
||
// Can't reveal before committing. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price, salt))); | ||
// Can commit a new hash. | ||
await voting.commitVote(identifier, time, hash); | ||
|
||
// Voters can alter their commits. | ||
const newPrice = getRandomInt(); | ||
const newSalt = getRandomInt(); | ||
const newHash = web3.utils.soliditySha3(newPrice, newSalt); | ||
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. Didn't know this function existed - nice! |
||
|
||
// Can't reveal before committing. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, newPrice, newSalt))); | ||
// Can alter a committed hash. | ||
await voting.commitVote(identifier, time, newHash); | ||
|
||
// Can't reveal the overwritten commit. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price, salt))); | ||
|
||
// Can't reveal with the wrong price but right salt, and reverse. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, newPrice, salt))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price, newSalt))); | ||
|
||
// Successfully reveal the latest commit. | ||
await voting.revealVote(identifier, time, newPrice, newSalt); | ||
|
||
// Can't reveal the same commit again. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, newPrice, newSalt))); | ||
}); | ||
|
||
it("Multiple voters", async function() { | ||
const voting = await Voting.new(); | ||
|
||
const identifier = web3.utils.utf8ToHex("id"); | ||
const time = "1000"; | ||
|
||
const price1 = getRandomInt(); | ||
const salt1 = getRandomInt(); | ||
const hash1 = web3.utils.soliditySha3(price1, salt1); | ||
|
||
const price2 = getRandomInt(); | ||
const salt2 = getRandomInt(); | ||
const hash2 = web3.utils.soliditySha3(price2, salt2); | ||
|
||
// Voter3 wants to vote the same price as voter1. | ||
const price3 = price1; | ||
const salt3 = getRandomInt(); | ||
const hash3 = web3.utils.soliditySha3(price3, salt3); | ||
|
||
// Multiple voters can commit. | ||
await voting.commitVote(identifier, time, hash1, { from: account1 }); | ||
await voting.commitVote(identifier, time, hash2, { from: account2 }); | ||
await voting.commitVote(identifier, time, hash3, { from: account3 }); | ||
|
||
// They can't reveal each other's votes. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price2, salt2, { from: account1 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price3, salt3, { from: account1 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price1, salt1, { from: account2 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price3, salt3, { from: account2 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price1, salt1, { from: account3 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price2, salt2, { from: account3 }))); | ||
|
||
// Someone who didn't even commit can't reveal anything either. | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price1, salt1, { from: account4 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price2, salt2, { from: account4 }))); | ||
assert(await didContractThrow(voting.revealVote(identifier, time, price3, salt3, { from: account4 }))); | ||
|
||
// They can reveal their own votes. | ||
await voting.revealVote(identifier, time, price1, salt1, { from: account1 }); | ||
await voting.revealVote(identifier, time, price2, salt2, { from: account2 }); | ||
await voting.revealVote(identifier, time, price3, salt3, { from: account3 }); | ||
}); | ||
|
||
it("Overlapping request keys", async function() { | ||
const voting = await Voting.new(); | ||
|
||
// Verify that concurrent votes with the same identifier but different times, or the same time but different | ||
// identifiers don't cause any problems. | ||
const identifier1 = web3.utils.utf8ToHex("id1"); | ||
const time1 = "1000"; | ||
const identifier2 = web3.utils.utf8ToHex("id2"); | ||
const time2 = "2000"; | ||
|
||
const price1 = getRandomInt(); | ||
const salt1 = getRandomInt(); | ||
const hash1 = web3.utils.soliditySha3(price1, salt1); | ||
|
||
const price2 = getRandomInt(); | ||
const salt2 = getRandomInt(); | ||
const hash2 = web3.utils.soliditySha3(price2, salt2); | ||
|
||
await voting.commitVote(identifier1, time2, hash1); | ||
await voting.commitVote(identifier2, time1, hash2); | ||
|
||
// Can't reveal the wrong combos. | ||
assert(await didContractThrow(voting.revealVote(identifier1, time2, price2, salt2))); | ||
assert(await didContractThrow(voting.revealVote(identifier2, time1, price1, salt1))); | ||
assert(await didContractThrow(voting.revealVote(identifier1, time1, price1, salt1))); | ||
assert(await didContractThrow(voting.revealVote(identifier1, time1, price2, salt2))); | ||
|
||
// Can reveal the right combos. | ||
voting.revealVote(identifier1, time2, price1, salt1); | ||
voting.revealVote(identifier2, time1, price2, salt2); | ||
}); | ||
}); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
I think we should add this contract to the migrations and use
.deployed()
. We can do so in a later PR, but just wanted to note that.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.
I'll do that in a follow up PR.