From c1b326ab9caec1915278a29ad3718e49af12813e Mon Sep 17 00:00:00 2001 From: Szymon Szlachtowicz <38212223+Szymx95@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:55:47 +0200 Subject: [PATCH] Add waku init poll mock (#5) --- packages/core/src/index.ts | 55 ++++++++++++++++--- packages/core/src/models/PollInitMsg.ts | 43 ++++++++++++++- packages/core/src/utils/proto/PollInit.ts | 26 ++------- packages/core/test/index.test.ts | 2 +- packages/core/test/models/PollInitMsg.test.ts | 26 ++------- .../core/test/utils/proto/PollInit.test.ts | 52 +++++++----------- 6 files changed, 118 insertions(+), 86 deletions(-) diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 986137a..c194e84 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,30 +1,67 @@ import { Waku, getStatusFleetNodes } from 'js-waku' +import { JsonRpcSigner } from '@ethersproject/providers' +import { PollInitMsg } from './models/PollInitMsg' +import { PollType } from './types/PollType' +import { BigNumber, Wallet } from 'ethers' +import PollInit from './utils/proto/PollInit' +import { WakuMessage } from 'js-waku' class WakuVoting { private appName: string private waku: Waku | undefined public tokenAddress: string + private pollInitTopic: string - private async createWaku() { - this.waku = await Waku.create() + private static async createWaku() { + const waku = await Waku.create() const nodes = await getStatusFleetNodes() await Promise.all( nodes.map((addr) => { - if (this.waku) { - return this.waku.dial(addr) + if (waku) { + return waku.dial(addr) } }) ) + return waku } - constructor(appName: string, tokenAddress: string, waku?: Waku) { + private constructor(appName: string, tokenAddress: string, waku: Waku) { this.appName = appName this.tokenAddress = tokenAddress - if (waku) { - this.waku = waku - } else { - this.createWaku() + this.pollInitTopic = `/${this.appName}/waku-polling/timed-polls-init/proto/` + this.waku = waku + } + + public static async create(appName: string, tokenAddress: string, waku?: Waku) { + if (!waku) { + waku = await this.createWaku() } + return new WakuVoting(appName, tokenAddress, waku) + } + + public async createTimedPoll( + signer: JsonRpcSigner | Wallet, + question: string, + answers: string[], + pollType: PollType, + minToken?: BigNumber, + endTime?: number + ) { + const pollInit = await PollInitMsg.create(signer, question, answers, pollType, minToken, endTime) + const payload = PollInit.encode(pollInit) + if (payload && pollInit) { + const wakuMessage = await WakuMessage.fromBytes(payload, this.pollInitTopic, { + timestamp: new Date(pollInit.timestamp), + }) + await this.waku?.relay.send(wakuMessage) + } + } + + public async getTimedPolls() { + const messages = await this.waku?.store.queryHistory({ contentTopics: [this.pollInitTopic] }) + return messages + ?.filter((e): e is WakuMessage & { payload: Uint8Array } => !!e?.payload) + .map((msg) => PollInit.decode(msg.payload, msg.timestamp)) } } diff --git a/packages/core/src/models/PollInitMsg.ts b/packages/core/src/models/PollInitMsg.ts index 509e8dc..18d9dad 100644 --- a/packages/core/src/models/PollInitMsg.ts +++ b/packages/core/src/models/PollInitMsg.ts @@ -1,7 +1,8 @@ import { PollType } from '../types/PollType' import { BigNumber, utils } from 'ethers' import { JsonRpcSigner } from '@ethersproject/providers' - +import { PollInit } from 'protons' +import { Wallet } from 'ethers' export class PollInitMsg { public owner: string public timestamp: number @@ -34,7 +35,7 @@ export class PollInitMsg { } static async create( - signer: JsonRpcSigner, + signer: JsonRpcSigner | Wallet, question: string, answers: string[], pollType: PollType, @@ -72,4 +73,42 @@ export class PollInitMsg { return new PollInitMsg(owner, signature, timestamp, question, answers, pollType, newEndTime, minToken) } + + static fromProto(payload: PollInit) { + const owner = utils.getAddress(utils.hexlify(payload.owner)) + const timestamp = payload.timestamp + const question = payload.question + const answers = payload.answers + const pollType = payload.pollType + const endTime = payload.endTime + const signature = utils.hexlify(payload.signature) + let minToken = payload.minToken ? BigNumber.from(payload.minToken) : undefined + + const msg: (string | number | BigNumber | PollType)[] = [ + owner, + timestamp, + question, + answers.join(), + pollType, + endTime, + ] + const types = ['address', 'uint256', 'string', 'string', 'uint8', 'uint256'] + if (pollType === PollType.NON_WEIGHTED) { + if (minToken) { + msg.push(minToken) + types.push('uint256') + } else { + return undefined + } + } else { + minToken = undefined + } + const packedData = utils.arrayify(utils.solidityPack(types, msg)) + const verifiedAddress = utils.verifyMessage(packedData, signature) + if (verifiedAddress != owner) { + return undefined + } + + return new PollInitMsg(owner, signature, timestamp, question, answers, pollType, endTime, minToken) + } } diff --git a/packages/core/src/utils/proto/PollInit.ts b/packages/core/src/utils/proto/PollInit.ts index 381473d..2e0c993 100644 --- a/packages/core/src/utils/proto/PollInit.ts +++ b/packages/core/src/utils/proto/PollInit.ts @@ -1,6 +1,6 @@ import protons, { PollInit } from 'protons' import { PollType } from '../../types/PollType' -import { utils, BigNumber } from 'ethers' +import { utils } from 'ethers' import { PollInitMsg } from '../../models/PollInitMsg' const proto = protons(` @@ -46,9 +46,12 @@ export function encode(pollInit: PollInitMsg) { } } -export function decode(payload: Uint8Array) { +export function decode(payload: Uint8Array, timestamp: Date | undefined) { try { const msg = proto.PollInit.decode(payload) + if (!timestamp || timestamp.getTime() != msg.timestamp) { + return undefined + } if ( msg.owner && msg.timestamp && @@ -58,24 +61,7 @@ export function decode(payload: Uint8Array) { msg.endTime && msg.signature ) { - const pollInit: PollInitMsg = { - owner: utils.hexlify(msg.owner), - timestamp: msg.timestamp, - question: msg.question, - answers: msg.answers, - pollType: msg.pollType, - endTime: msg.endTime, - signature: utils.hexlify(msg.signature), - } - - if (msg.pollType === PollType.NON_WEIGHTED) { - if (msg.minToken) { - pollInit.minToken = BigNumber.from(msg.minToken) - return pollInit - } - } else { - return pollInit - } + return PollInitMsg.fromProto(msg) } } catch { return undefined diff --git a/packages/core/test/index.test.ts b/packages/core/test/index.test.ts index 544ee79..9912545 100644 --- a/packages/core/test/index.test.ts +++ b/packages/core/test/index.test.ts @@ -4,7 +4,7 @@ import WakuVoting from '../src' describe('WakuVoting', () => { it('success', async () => { - const wakuVoting = new WakuVoting('test', '0x0', {} as unknown as Waku) + const wakuVoting = await WakuVoting.create('test', '0x0', {} as unknown as Waku) expect(wakuVoting).to.not.be.undefined }) diff --git a/packages/core/test/models/PollInitMsg.test.ts b/packages/core/test/models/PollInitMsg.test.ts index 0490e25..a382eb2 100644 --- a/packages/core/test/models/PollInitMsg.test.ts +++ b/packages/core/test/models/PollInitMsg.test.ts @@ -2,7 +2,6 @@ import { expect } from 'chai' import { PollInitMsg } from '../../src/models/PollInitMsg' import { MockProvider } from 'ethereum-waffle' import { PollType } from '../../src/types/PollType' -import { JsonRpcSigner } from '@ethersproject/providers' import { BigNumber, utils } from 'ethers' describe('PollInitMsg', () => { @@ -10,12 +9,7 @@ describe('PollInitMsg', () => { const [alice] = provider.getWallets() it('success', async () => { - const poll = await PollInitMsg.create( - alice as unknown as JsonRpcSigner, - 'test', - ['one', 'two', 'three'], - PollType.WEIGHTED - ) + const poll = await PollInitMsg.create(alice, 'test', ['one', 'two', 'three'], PollType.WEIGHTED) expect(poll).to.not.be.undefined expect(poll.owner).to.eq(alice.address) @@ -35,7 +29,7 @@ describe('PollInitMsg', () => { it('success NON_WEIGHTED', async () => { const poll = await PollInitMsg.create( - alice as unknown as JsonRpcSigner, + alice, 'test', ['one', 'two', 'three'], PollType.NON_WEIGHTED, @@ -61,12 +55,7 @@ describe('PollInitMsg', () => { }) it('NON_WEIGHTED no minToken', async () => { - const poll = await PollInitMsg.create( - alice as unknown as JsonRpcSigner, - 'test', - ['one', 'two', 'three'], - PollType.NON_WEIGHTED - ) + const poll = await PollInitMsg.create(alice, 'test', ['one', 'two', 'three'], PollType.NON_WEIGHTED) expect(poll?.minToken?.toNumber()).to.eq(1) @@ -87,14 +76,7 @@ describe('PollInitMsg', () => { }) it('specific end time', async () => { - const poll = await PollInitMsg.create( - alice as unknown as JsonRpcSigner, - 'test', - ['one', 'two', 'three'], - PollType.NON_WEIGHTED, - undefined, - 100 - ) + const poll = await PollInitMsg.create(alice, 'test', ['one', 'two', 'three'], PollType.NON_WEIGHTED, undefined, 100) expect(poll?.endTime).to.eq(100) }) diff --git a/packages/core/test/utils/proto/PollInit.test.ts b/packages/core/test/utils/proto/PollInit.test.ts index 78cb961..a31cdb0 100644 --- a/packages/core/test/utils/proto/PollInit.test.ts +++ b/packages/core/test/utils/proto/PollInit.test.ts @@ -3,29 +3,24 @@ import { PollType } from '../../../src/types/PollType' import PollInit from '../../../src/utils/proto/PollInit' import { BigNumber } from 'ethers' import { PollInitMsg } from '../../../src/models/PollInitMsg' +import { MockProvider } from 'ethereum-waffle' describe('PollInit', () => { + const provider = new MockProvider() + const [alice] = provider.getWallets() it('success', async () => { - const data: PollInitMsg = { - owner: '0x02', - answers: ['ab', 'cd', 'ef'], - endTime: 10, - pollType: PollType.WEIGHTED, - question: 'whats up', - signature: '0x11', - timestamp: 10, - } + const data = await PollInitMsg.create(alice, 'whats up', ['ab', 'cd', 'ef'], PollType.WEIGHTED) const payload = PollInit.encode(data) expect(payload).to.not.be.undefined if (payload) { - expect(PollInit.decode(payload)).to.deep.eq(data) + expect(PollInit.decode(payload, new Date(data.timestamp))).to.deep.eq(data) } }) it('random decode', async () => { - expect(PollInit.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]))).to.be.undefined + expect(PollInit.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be.undefined }) it('random data', async () => { @@ -33,36 +28,29 @@ describe('PollInit', () => { }) it('NON_WEIGHTED init', async () => { - const data: PollInitMsg = { - owner: '0x02', - answers: ['ab', 'cd', 'ef'], - endTime: 10, - pollType: PollType.NON_WEIGHTED, - minToken: BigNumber.from(10), - question: 'whats up', - signature: '0x11', - timestamp: 10, - } + const data = await PollInitMsg.create( + alice, + 'whats up', + ['ab', 'cd', 'ef'], + PollType.NON_WEIGHTED, + BigNumber.from(10) + ) + const payload = PollInit.encode(data) expect(payload).to.not.be.undefined if (payload) { - expect(PollInit.decode(payload)).to.deep.eq(data) + expect(PollInit.decode(payload, new Date(data.timestamp))).to.deep.eq(data) } }) it('NON_WEIGHTED no min token', async () => { - const data: PollInitMsg = { - owner: '0x02', - answers: ['ab', 'cd', 'ef'], - endTime: 10, - pollType: PollType.NON_WEIGHTED, - question: 'whats up', - signature: '0x11', - timestamp: 10, - } + const data = await PollInitMsg.create(alice, 'whats up', ['ab', 'cd', 'ef'], PollType.NON_WEIGHTED) const payload = PollInit.encode(data) - expect(payload).to.be.undefined + expect(payload).to.not.be.undefined + if (payload) { + expect(PollInit.decode(payload, new Date(data.timestamp))).to.deep.eq({ ...data, minToken: BigNumber.from(1) }) + } }) })