Skip to content
This repository has been archived by the owner on Sep 28, 2020. It is now read-only.

Commit

Permalink
Timestamp for voting windows
Browse files Browse the repository at this point in the history
  • Loading branch information
bonustrack committed Jul 21, 2020
1 parent 20fa2c5 commit 0c801f3
Show file tree
Hide file tree
Showing 26 changed files with 556 additions and 321 deletions.
71 changes: 70 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "balancer-gov",
"version": "0.1.0",
"version": "0.1.1",
"repository": "balancer-labs/balancer-gov",
"license": "GPL-3.0",
"scripts": {
Expand Down Expand Up @@ -42,6 +42,8 @@
"numeral": "^2.0.4",
"primer-support": "^5.0.0",
"redis": "^3.0.2",
"remarkable": "^2.0.1",
"sanitize-html": "^1.27.1",
"serve-static": "^1.14.1",
"stylus": "^0.54.8",
"stylus-loader": "^3.0.2",
Expand Down
92 changes: 45 additions & 47 deletions server/api.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
import express from 'express';
import { getDefaultProvider } from '@ethersproject/providers';
import redis from './redis';
import pinata from './pinata';
import relayer from './relayer';
import { verify, jsonParse } from './utils';
const router = express.Router();

let currentBlockNumber: number = 0;
import { verify, jsonParse, sendError } from './utils';
import pkg from '../package.json';

setInterval(async () => {
const defaultProvider = getDefaultProvider();
const blockNumber: any = await defaultProvider.getBlockNumber();
currentBlockNumber = parseInt(blockNumber);
await redis.setAsync('currentBlockNumber', currentBlockNumber);
console.log('Block number', currentBlockNumber);
}, 8e3)
const router = express.Router();

router.get('/:token/proposals', async (req, res) => {
const { token } = req.params;
Expand All @@ -41,58 +32,65 @@ router.get('/:token/proposal/:id', async (req, res) => {
router.post('/message', async (req, res) => {
const body = req.body;
const msg = jsonParse(body.msg);
const ts = (Date.now() / 1e3).toFixed();
const minBlock = (3600 * 24) / 15;

if (!body || !body.address || !body.msg || !body.sig)
return sendError(res, 'wrong message body');

if (
!currentBlockNumber ||
!body ||
!body.address ||
!body.msg ||
!body.sig ||
Object.keys(msg).length !== 5 ||
!msg.version ||
!msg.token ||
!msg.type ||
!['proposal', 'vote'].includes(msg.type) ||
Object.keys(msg.payload).length === 0 ||
msg.type === 'proposal' && (
Object.keys(msg.payload).length !== 5 ||
!msg.payload ||
Object.keys(msg.payload).length === 0
) return sendError(res, 'wrong signed message');

if (!msg.timestamp || typeof msg.timestamp !== 'string' || msg.timestamp > ts)
return sendError(res, 'wrong timestamp');

if (!msg.version || msg.version !== pkg.version)
return sendError(res, 'wrong version');

if (!msg.type || !['proposal', 'vote'].includes(msg.type))
return sendError(res, 'wrong message type');

if (!await verify(body.address, body.msg, body.sig))
return sendError(res, 'wrong signature');

if (msg.type === 'proposal') {
if (
Object.keys(msg.payload).length !== 6 ||
!msg.payload.name ||
msg.payload.name.length > 128 ||
!msg.payload.body ||
msg.payload.body.length > 10240 ||
msg.payload.body.length > 5120 ||
!msg.payload.choices ||
msg.payload.choices.length < 2 ||
!msg.payload.startBlock ||
currentBlockNumber > msg.payload.startBlock ||
!msg.payload.endBlock ||
msg.payload.startBlock >= msg.payload.endBlock
) ||
msg.type === 'vote' && (
Object.keys(msg.payload).length !== 2 ||
!msg.payload.proposal ||
!msg.payload.choice
)
) {
console.log('unauthorized', body);
return res.status(500).json({ error: 'unauthorized' });
}
!msg.payload.snapshot
) return sendError(res, 'wrong format proposal');

if (!await verify(body.address, body.msg, body.sig)) {
console.log('wrong signature', body);
return res.status(500).json({ error: 'wrong signature' });
if (
!msg.payload.start ||
ts > msg.payload.start ||
!msg.payload.end ||
msg.payload.start >= msg.payload.end
) return sendError(res, 'wrong proposal period');
}

if (msg.type === 'vote') {
if (
Object.keys(msg.payload).length !== 2 ||
!msg.payload.proposal ||
!msg.payload.choice
) return sendError(res, 'wrong format vote');

const proposalRedis = await redis.hgetAsync(`token:${msg.token}:proposals`, msg.payload.proposal);
const proposal = jsonParse(proposalRedis);
if (
!proposalRedis ||
currentBlockNumber > proposal.msg.payload.endBlock ||
proposal.msg.payload.startBlock > currentBlockNumber
) {
console.log('wrong vote', body);
return res.status(500).json({ error: 'wrong vote' });
}
ts > proposal.msg.payload.end ||
proposal.msg.payload.start > ts
) return sendError(res, 'wrong vote');
}

const authorIpfsRes = await pinata.pinJSONToIPFS({
Expand Down
7 changes: 7 additions & 0 deletions server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ export async function verify(address, msg, sig) {
export function clone(item) {
return JSON.parse(JSON.stringify(item));
}

export function sendError(res, description) {
return res.status(500).json({
error: 'unauthorized',
error_description: description
});
}
14 changes: 6 additions & 8 deletions src/components/Block/Votes.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@
class="px-4 py-3 border-top d-flex"
>
<User :address="address" :verified="token.verified" class="column" />
<div class="flex-auto text-center">
<span
v-text="proposal.msg.payload.choices[vote.msg.payload.choice - 1]"
class="text-white ml-2 column"
/>
</div>
<div
v-text="proposal.msg.payload.choices[vote.msg.payload.choice - 1]"
class="flex-auto text-center text-white"
/>
<div class="column text-right">
<span
v-text="
Expand All @@ -28,7 +26,7 @@
<a
@click="openReceiptModal(vote)"
target="_blank"
class="ml-3"
class="ml-3 text-gray"
title="Receipt"
>
<Icon name="signature" />
Expand All @@ -38,7 +36,7 @@
<a
v-if="!showAllVotes && Object.keys(votes).length > 10"
@click="showAllVotes = true"
class="px-4 py-3 border-top text-center d-block text-white bg-gray-dark"
class="px-4 py-3 border-top text-center d-block bg-gray-dark"
>
See more
</a>
Expand Down
17 changes: 16 additions & 1 deletion src/components/Modal/About.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,28 @@
<UiModal :open="open" @close="$emit('close')">
<h3 class="m-4 mb-0 text-center">About</h3>
<div class="m-4 mb-0 p-4 border rounded-2 text-white">
<div class="d-flex">
<span v-text="'Version'" class="flex-auto text-gray mr-1" />
{{ pkg.version }}
</div>
<div class="d-flex">
<span v-text="'License'" class="flex-auto text-gray mr-1" />
{{ pkg.license }}
</div>
<div class="d-flex">
<span v-text="'Network'" class="flex-auto text-gray mr-1" />
{{ network === 'homestead' ? 'mainnet' : network }}
</div>
<div class="d-flex">
<span v-text="'Block number'" class="flex-auto text-gray mr-1" />
{{ web3.blockNumber }}
<a
:href="_etherscanLink(web3.blockNumber, 'block')"
target="_blank"
class="float-right"
>
{{ $n(web3.blockNumber) }}
<Icon name="external-link" class="ml-1" />
</a>
</div>
<div class="d-flex">
<span v-text="'IPFS server'" class="flex-auto text-gray mr-1" />
Expand Down
13 changes: 8 additions & 5 deletions src/components/Modal/Confirm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<div class="m-4 flex-auto text-center">
<h4>Are you sure you want to vote for this option?</h4>
<h4>This action <b>cannot</b> be undone.</h4>
<h4 class="p-3 my-3 border rounded-2 text-white">
<h4 class="p-3 my-3 border rounded-2">
Option {{ selectedChoice }}:
{{ proposal.msg.payload.choices[selectedChoice - 1] }}
</h4>
Expand Down Expand Up @@ -42,13 +42,16 @@ export default {
};
},
methods: {
...mapActions(['vote']),
...mapActions(['send']),
async handleSubmit() {
this.loading = true;
await this.vote({
await this.send({
token: this.token,
proposal: this.id,
choice: this.selectedChoice
type: 'vote',
payload: {
proposal: this.id,
choice: this.selectedChoice
}
});
this.$emit('reload');
this.$emit('close');
Expand Down
23 changes: 23 additions & 0 deletions src/components/Modal/SelectDate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<UiModal :open="open" @close="$emit('close')">
<h3 class="m-4 text-center">Select a date</h3>
<div class="modal-body">
<UiCalendar
@input="[$emit('input', input), $emit('close')]"
v-model="input"
class="mx-auto mb-7"
/>
</div>
</UiModal>
</template>

<script>
export default {
props: ['open'],
data() {
return {
input: ''
};
}
};
</script>
Loading

0 comments on commit 0c801f3

Please sign in to comment.