diff --git a/package-lock.json b/package-lock.json
index 06406c87..63439cc9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "balancer-gov",
- "version": "0.1.0",
+ "version": "0.1.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -2987,6 +2987,14 @@
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg=="
},
+ "autolinker": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.14.1.tgz",
+ "integrity": "sha512-yvsRHIaY51EYDml6MGlbqyJGfl4n7zezGYf+R7gvM8c5LNpRGc4SISkvgAswSS8SWxk/OrGCylKV9mJyVstz7w==",
+ "requires": {
+ "tslib": "^1.9.3"
+ }
+ },
"autoprefixer": {
"version": "9.8.0",
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.8.0.tgz",
@@ -12118,6 +12126,15 @@
"resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
"integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
},
+ "remarkable": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz",
+ "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==",
+ "requires": {
+ "argparse": "^1.0.10",
+ "autolinker": "^3.11.0"
+ }
+ },
"remove-trailing-separator": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz",
@@ -12400,6 +12417,53 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
+ "sanitize-html": {
+ "version": "1.27.1",
+ "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.27.1.tgz",
+ "integrity": "sha512-C+N7E+7ikYaLHdb9lEkQaFOgmj+9ddZ311Ixs/QsBsoLD411/vdLweiFyGqrswUVgLqagOS5NCDxcEPH7trObQ==",
+ "requires": {
+ "htmlparser2": "^4.1.0",
+ "lodash": "^4.17.15",
+ "postcss": "^7.0.27",
+ "srcset": "^2.0.1"
+ },
+ "dependencies": {
+ "domelementtype": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz",
+ "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ=="
+ },
+ "domhandler": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz",
+ "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==",
+ "requires": {
+ "domelementtype": "^2.0.1"
+ }
+ },
+ "domutils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.1.0.tgz",
+ "integrity": "sha512-CD9M0Dm1iaHfQ1R/TI+z3/JWp/pgub0j4jIQKH89ARR4ATAV2nbaOQS5XxU9maJP5jHaPdDDQSEHuE2UmpUTKg==",
+ "requires": {
+ "dom-serializer": "^0.2.1",
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0"
+ }
+ },
+ "htmlparser2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz",
+ "integrity": "sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==",
+ "requires": {
+ "domelementtype": "^2.0.1",
+ "domhandler": "^3.0.0",
+ "domutils": "^2.0.0",
+ "entities": "^2.0.0"
+ }
+ }
+ }
+ },
"sass-graph": {
"version": "2.2.5",
"resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.5.tgz",
@@ -13176,6 +13240,11 @@
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
+ "srcset": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/srcset/-/srcset-2.0.1.tgz",
+ "integrity": "sha512-00kZI87TdRKwt+P8jj8UZxbfp7mK2ufxcIMWvhAOZNJTRROimpHeruWrGvCZneiuVDLqdyHefVp748ECTnyUBQ=="
+ },
"sshpk": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
diff --git a/package.json b/package.json
index 91935aa5..c5458875 100644
--- a/package.json
+++ b/package.json
@@ -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": {
@@ -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",
diff --git a/server/api.ts b/server/api.ts
index ad5ac7fc..09810dac 100644
--- a/server/api.ts
+++ b/server/api.ts
@@ -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;
@@ -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({
diff --git a/server/utils.ts b/server/utils.ts
index becfba96..c62d327b 100644
--- a/server/utils.ts
+++ b/server/utils.ts
@@ -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
+ });
+}
diff --git a/src/components/Block/Votes.vue b/src/components/Block/Votes.vue
index abd16c66..b9255dd2 100644
--- a/src/components/Block/Votes.vue
+++ b/src/components/Block/Votes.vue
@@ -12,12 +12,10 @@
class="px-4 py-3 border-top d-flex"
>