-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
polling: Add PollCreator and Poll contracts (#373)
* polling: Add PollCreator and Poll contracts Co-authored-by: Nico Vergauwen <nico@Nicos-MacBook-Pro.local>
- Loading branch information
Showing
5 changed files
with
237 additions
and
0 deletions.
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.11; | ||
|
||
|
||
contract Poll { | ||
// The block at which the poll ends and votes can no longer be submitted. | ||
uint256 public endBlock; | ||
|
||
// Vote is emitted when an account submits a vote with 'choiceID'. | ||
// This event can be indexed to tally all votes for each choiceID | ||
event Vote(address indexed voter, uint256 choiceID); | ||
|
||
modifier isActive() { | ||
require( | ||
block.number <= endBlock, | ||
"poll is over" | ||
); | ||
_; | ||
} | ||
|
||
constructor(uint256 _endBlock) public { | ||
endBlock = _endBlock; | ||
} | ||
|
||
/** | ||
* @dev Vote for the poll's proposal. | ||
* Reverts if the poll period is over. | ||
* @param _choiceID the ID of the option to vote for | ||
*/ | ||
function vote(uint256 _choiceID) external isActive { | ||
emit Vote(msg.sender, _choiceID); | ||
} | ||
|
||
/** | ||
* @dev Destroy the Poll contract after the poll has finished | ||
* Reverts if the poll is still active | ||
*/ | ||
function destroy() external { | ||
require(block.number > endBlock, "poll is active"); | ||
selfdestruct(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,52 @@ | ||
pragma solidity ^0.5.11; | ||
|
||
import "./Poll.sol"; | ||
import "../token/ILivepeerToken.sol"; | ||
|
||
|
||
contract PollCreator { | ||
// TODO: Update these values | ||
uint256 public constant QUORUM = 20; | ||
uint256 public constant THRESHOLD = 50; | ||
uint256 public constant POLL_PERIOD = 10 * 5760; | ||
uint256 public constant POLL_CREATION_COST = 100 * 1 ether; | ||
|
||
ILivepeerToken public token; | ||
|
||
event PollCreated( | ||
address indexed poll, | ||
bytes proposal, | ||
uint256 endBlock, | ||
uint256 quorum, | ||
uint256 threshold | ||
); | ||
|
||
constructor(address _tokenAddr) public { | ||
token = ILivepeerToken(_tokenAddr); | ||
} | ||
|
||
/** | ||
* @dev Create a poll by burning POLL_CREATION_COST LPT. | ||
* Reverts if this contract's LPT allowance for the sender < POLL_CREATION_COST. | ||
* @param _proposal The IPFS multihash for the proposal. | ||
*/ | ||
function createPoll(bytes calldata _proposal) external { | ||
uint256 endBlock = block.number + POLL_PERIOD; | ||
Poll poll = new Poll(endBlock); | ||
|
||
require( | ||
token.transferFrom(msg.sender, address(this), POLL_CREATION_COST), | ||
"LivepeerToken transferFrom failed" | ||
); | ||
|
||
token.burn(POLL_CREATION_COST); | ||
|
||
emit PollCreated( | ||
address(poll), | ||
_proposal, | ||
endBlock, | ||
QUORUM, | ||
THRESHOLD | ||
); | ||
} | ||
} |
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,13 @@ | ||
const {contractId} = require("../utils/helpers") | ||
|
||
const Controller = artifacts.require("Controller") | ||
const PollCreator = artifacts.require("PollCreator") | ||
|
||
module.exports = function(deployer) { | ||
deployer.then(async () => { | ||
const controller = await Controller.deployed() | ||
const tokenAddr = await controller.getContract(contractId("LivepeerToken")) | ||
|
||
await deployer.deploy(PollCreator, tokenAddr) | ||
}) | ||
} |
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,61 @@ | ||
import truffleAssert from "truffle-assertions" | ||
|
||
import Fixture from "./helpers/Fixture" | ||
import expectRevertWithReason from "../helpers/expectFail" | ||
|
||
const Poll = artifacts.require("Poll") | ||
|
||
contract("Poll", accounts => { | ||
let fixture | ||
let poll | ||
let startBlock | ||
let endBlock | ||
|
||
before(() => { | ||
fixture = new Fixture(web3) | ||
}) | ||
|
||
beforeEach(async () => { | ||
await fixture.setUp() | ||
startBlock = await fixture.rpc.getBlockNumberAsync() | ||
endBlock = startBlock + 10 | ||
poll = await Poll.new(endBlock) | ||
}) | ||
|
||
afterEach(async () => { | ||
await fixture.tearDown() | ||
}) | ||
|
||
describe("constructor", () => { | ||
it("initialize state: endBlock", async () => { | ||
assert.equal((await poll.endBlock()).toNumber(), endBlock) | ||
}) | ||
}) | ||
|
||
describe("vote", () => { | ||
it("emit \"Vote\" event when poll is active", async () => { | ||
let tx = await poll.vote(0) | ||
truffleAssert.eventEmitted(tx, "Vote", e => e.voter == accounts[0] && e.choiceID == 0, "Vote event not emitted correctly") | ||
tx = await poll.vote(1, {from: accounts[1]}) | ||
truffleAssert.eventEmitted(tx, "Vote", e => e.voter == accounts[1] && e.choiceID == 1, "Vote event not emitted correctly") | ||
}) | ||
|
||
it("revert when poll is inactive", async () => { | ||
await fixture.rpc.waitUntilBlock(endBlock + 1) | ||
await expectRevertWithReason(poll.vote(0), "poll is over") | ||
}) | ||
}) | ||
|
||
describe("destroy", () => { | ||
it("revert when poll is active", async () => { | ||
await expectRevertWithReason(poll.destroy(), "poll is active") | ||
}) | ||
|
||
it("destroy the contract when poll has ended", async () => { | ||
await fixture.rpc.waitUntilBlock(endBlock + 1) | ||
let tx = await poll.destroy() | ||
assert.equal(await web3.eth.getCode(poll.address), "0x") | ||
assert.equal(tx.receipt.status, true) | ||
}) | ||
}) | ||
}) |
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,70 @@ | ||
import truffleAssert from "truffle-assertions" | ||
|
||
import Fixture from "./helpers/Fixture" | ||
import expectRevertWithReason from "../helpers/expectFail" | ||
import {functionSig} from "../../utils/helpers" | ||
|
||
const PollCreator = artifacts.require("PollCreator") | ||
const GenericMock = artifacts.require("GenericMock") | ||
|
||
const QUORUM = 20 | ||
const THRESHOLD = 50 | ||
const POLL_PERIOD = 10 * 5760 | ||
|
||
contract("PollCreator", accounts => { | ||
let fixture | ||
let token | ||
let pollCreator | ||
|
||
before(async () => { | ||
fixture = new Fixture(web3) | ||
token = await GenericMock.new() | ||
}) | ||
|
||
beforeEach(async () => { | ||
await fixture.setUp() | ||
}) | ||
|
||
afterEach(async () => { | ||
await fixture.tearDown() | ||
}) | ||
|
||
describe("constructor", () => { | ||
before(async () => { | ||
pollCreator = await PollCreator.new(token.address) | ||
}) | ||
|
||
it("initialize state: token", async () => { | ||
assert.equal(await pollCreator.token(), token.address) | ||
}) | ||
}) | ||
|
||
describe("createPoll", () => { | ||
const hash = "0x1230000000000000000000000000000000000000" | ||
|
||
before(async () => { | ||
pollCreator = await PollCreator.new(token.address) | ||
}) | ||
|
||
it("revert when not enough tokens approved", async () => { | ||
await expectRevertWithReason(pollCreator.createPoll(hash), "LivepeerToken transferFrom failed") | ||
}) | ||
|
||
it("creates a poll", async () => { | ||
await token.setMockBool(functionSig("transferFrom(address,address,uint256)"), true) | ||
let start = await fixture.rpc.getBlockNumberAsync() | ||
let end = start + POLL_PERIOD + 1 // + 1 because createPoll tx will mine a new block | ||
let tx = await pollCreator.createPoll(hash) | ||
truffleAssert.eventEmitted( | ||
tx, | ||
"PollCreated", | ||
e => e.proposal == hash | ||
&& e.endBlock.toNumber() == end | ||
&& e.quorum.toNumber() == QUORUM | ||
&& e.threshold.toNumber() == THRESHOLD | ||
, | ||
"PollCreated event not emitted correctly" | ||
) | ||
}) | ||
}) | ||
}) |