Skip to content

Commit

Permalink
polling: Add PollCreator and Poll contracts (#373)
Browse files Browse the repository at this point in the history
* polling: Add PollCreator and Poll contracts

Co-authored-by: Nico Vergauwen <nico@Nicos-MacBook-Pro.local>
  • Loading branch information
yondonfu and Nico Vergauwen committed Apr 20, 2020
1 parent d6bcb03 commit c25bb8e
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 0 deletions.
41 changes: 41 additions & 0 deletions contracts/polling/Poll.sol
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);
}
}
52 changes: 52 additions & 0 deletions contracts/polling/PollCreator.sol
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
);
}
}
13 changes: 13 additions & 0 deletions migrations/5_deploy_poll.js
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)
})
}
61 changes: 61 additions & 0 deletions test/unit/Poll.js
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)
})
})
})
70 changes: 70 additions & 0 deletions test/unit/PollCreator.js
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"
)
})
})
})

0 comments on commit c25bb8e

Please sign in to comment.