Make core dependent on chainId (#50)

This commit is contained in:
Szymon Szlachtowicz 2021-09-03 14:41:29 +02:00 committed by GitHub
parent 14a1141d8d
commit 1623d8e023
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 132 additions and 80 deletions

View File

@ -8,10 +8,11 @@ import { TimedPollVoteMsg } from '../models/TimedPollVoteMsg'
import { DetailedTimedPoll } from '../models/DetailedTimedPoll'
import { createWaku } from '../utils/createWaku'
import { WakuVoting } from './WakuVoting'
import { Provider } from '@ethersproject/providers'
export class WakuPolling extends WakuVoting {
protected constructor(appName: string, tokenAddress: string, waku: Waku) {
super(appName, tokenAddress, waku)
protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) {
super(appName, tokenAddress, waku, provider, chainId)
this.wakuMessages['pollInit'] = {
topic: `/${this.appName}/waku-polling/timed-polls-init/proto/`,
hashMap: {},
@ -34,8 +35,9 @@ export class WakuPolling extends WakuVoting {
this.setObserver()
}
public static async create(appName: string, tokenAddress: string, waku?: Waku) {
const wakuPolling = new WakuPolling(appName, tokenAddress, await createWaku(waku))
public static async create(appName: string, tokenAddress: string, provider: Provider, waku?: Waku) {
const network = await provider.getNetwork()
const wakuPolling = new WakuPolling(appName, tokenAddress, await createWaku(waku), provider, network.chainId)
return wakuPolling
}
@ -47,7 +49,7 @@ export class WakuPolling extends WakuVoting {
minToken?: BigNumber,
endTime?: number
) {
const pollInit = await PollInitMsg.create(signer, question, answers, pollType, minToken, endTime)
const pollInit = await PollInitMsg.create(signer, question, answers, pollType, this.chainId, minToken, endTime)
await this.sendWakuMessage(this.wakuMessages['pollInit'], pollInit)
}
@ -57,7 +59,7 @@ export class WakuPolling extends WakuVoting {
selectedAnswer: number,
tokenAmount?: BigNumber
) {
const pollVote = await TimedPollVoteMsg.create(signer, pollId, selectedAnswer, tokenAmount)
const pollVote = await TimedPollVoteMsg.create(signer, pollId, selectedAnswer, this.chainId, tokenAmount)
await this.sendWakuMessage(this.wakuMessages['pollVote'], pollVote)
}

View File

@ -1,6 +1,7 @@
import { Waku } from 'js-waku'
import { WakuMessage } from 'js-waku'
import { createWaku } from '../utils/createWaku'
import { Provider } from '@ethersproject/providers'
type WakuMessageStore = {
topic: string
@ -17,21 +18,27 @@ export class WakuVoting {
protected appName: string
protected waku: Waku
public tokenAddress: string
protected provider: Provider
protected chainId = 0
protected wakuMessages: WakuMessageStores = {}
protected observers: { callback: (msg: WakuMessage) => void; topics: string[] }[] = []
protected constructor(appName: string, tokenAddress: string, waku: Waku) {
protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) {
this.appName = appName
this.tokenAddress = tokenAddress
this.waku = waku
this.provider = provider
this.chainId = chainId
}
public static async create(appName: string, tokenAddress: string, waku?: Waku) {
return new WakuVoting(appName, tokenAddress, await createWaku(waku))
public static async create(appName: string, tokenAddress: string, provider: Provider, waku?: Waku) {
const network = await provider.getNetwork()
const wakuVoting = new WakuVoting(appName, tokenAddress, await createWaku(waku), provider, network.chainId)
return wakuVoting
}
public cleanUp() {
this.observers.forEach((observer) => this.waku.relay.deleteObserver(observer.callback, observer.topics))
this.wakuMessages = {}
}
protected async setObserver() {
@ -49,12 +56,12 @@ export class WakuVoting {
protected decodeMsgAndSetArray<T extends { id: string; timestamp: number }>(
messages: WakuMessage[],
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined) => T | undefined,
decode: (payload: Uint8Array | undefined, timestamp: Date | undefined, chainId: number) => T | undefined,
msgObj: WakuMessageStore,
filterFunction?: (e: T) => boolean
) {
messages
.map((msg) => decode(msg.payload, msg.timestamp))
.map((msg) => decode(msg.payload, msg.timestamp, this.chainId))
.sort((a, b) => ((a?.timestamp ?? new Date(0)) > (b?.timestamp ?? new Date(0)) ? 1 : -1))
.forEach((e) => {
if (e) {

View File

@ -2,7 +2,7 @@ import { PollType } from '../types/PollType'
import { BigNumber, utils, Wallet } from 'ethers'
import { JsonRpcSigner } from '@ethersproject/providers'
import protons, { PollInit } from 'protons'
import { createSignedMsg } from '../utils/createSignedMsg'
import { createSignFunction } from '../utils/createSignFunction'
import { verifySignature } from '../utils/verifySignature'
const proto = protons(`
@ -32,11 +32,12 @@ type Message = {
minToken?: BigNumber
}
export function createSignMsgParams(message: Message) {
export function createSignMsgParams(message: Message, chainId: number) {
const msgParams: any = {
domain: {
name: 'Waku polling',
version: '1',
chainId,
},
message: {
...message,
@ -49,6 +50,7 @@ export function createSignMsgParams(message: Message) {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
],
Mail: [
{ name: 'owner', type: 'address' },
@ -78,8 +80,8 @@ export class PollInitMsg {
public endTime: number
public signature: string
public id: string
constructor(signature: string, msg: Message) {
public chainId: number
constructor(signature: string, msg: Message, chainId: number) {
this.id = utils.id([msg.owner, msg.timestamp, signature].join())
this.signature = signature
this.owner = msg.owner
@ -89,18 +91,16 @@ export class PollInitMsg {
this.pollType = msg.pollType
this.minToken = msg.minToken
this.endTime = msg.endTime
this.chainId = chainId
}
static async _createWithSignFunction(
signFunction: (
msg: any,
params: string[],
Class: new (sig: string, msg: any) => PollInitMsg
) => Promise<PollInitMsg | undefined>,
signFunction: (params: string[]) => Promise<string | undefined>,
signer: JsonRpcSigner | Wallet,
question: string,
answers: string[],
pollType: PollType,
chainId: number,
minToken?: BigNumber,
endTime?: number
): Promise<PollInitMsg | undefined> {
@ -117,8 +117,13 @@ export class PollInitMsg {
endTime: endTime ? endTime : timestamp + 100000000,
minToken,
}
const params = [msg.owner, JSON.stringify(createSignMsgParams(msg))]
return signFunction(msg, params, PollInitMsg)
const params = [msg.owner, JSON.stringify(createSignMsgParams(msg, chainId))]
const signature = await signFunction(params)
if (signature) {
return new PollInitMsg(signature, msg, chainId)
} else {
return undefined
}
}
static async create(
@ -126,10 +131,20 @@ export class PollInitMsg {
question: string,
answers: string[],
pollType: PollType,
chainId: number,
minToken?: BigNumber,
endTime?: number
): Promise<PollInitMsg | undefined> {
return this._createWithSignFunction(createSignedMsg(signer), signer, question, answers, pollType, minToken, endTime)
return this._createWithSignFunction(
createSignFunction(signer),
signer,
question,
answers,
pollType,
chainId,
minToken,
endTime
)
}
encode() {
@ -161,6 +176,7 @@ export class PollInitMsg {
static decode(
rawPayload: Uint8Array | undefined,
timestamp: Date | undefined,
chainId: number,
verifyFunction?: (params: any, address: string) => boolean
) {
try {
@ -180,13 +196,13 @@ export class PollInitMsg {
}
const signature = utils.hexlify(payload.signature)
const params = {
data: createSignMsgParams(msg),
data: createSignMsgParams(msg, chainId),
sig: signature,
}
if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.owner)) {
return undefined
}
return new PollInitMsg(signature, msg)
return new PollInitMsg(signature, msg, chainId)
} catch {
return undefined
}

View File

@ -2,7 +2,7 @@ import { BigNumber, utils } from 'ethers'
import { JsonRpcSigner } from '@ethersproject/providers'
import protons, { TimedPollVote } from 'protons'
import { Wallet } from 'ethers'
import { createSignedMsg } from '../utils/createSignedMsg'
import { createSignFunction } from '../utils/createSignFunction'
import { verifySignature } from '../utils/verifySignature'
const proto = protons(`
@ -24,11 +24,12 @@ type Message = {
tokenAmount?: BigNumber
}
export function createSignMsgParams(message: Message) {
export function createSignMsgParams(message: Message, chainId: number) {
const msgParams: any = {
domain: {
name: 'Waku polling',
version: '1',
chainId,
},
message: {
...message,
@ -39,6 +40,7 @@ export function createSignMsgParams(message: Message) {
EIP712Domain: [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
],
Mail: [
{ name: 'pollId', type: 'string' },
@ -64,8 +66,8 @@ export class TimedPollVoteMsg {
public tokenAmount?: BigNumber
public signature: string
public id: string
constructor(signature: string, msg: Message) {
public chainId: number
constructor(signature: string, msg: Message, chainId: number) {
this.id = utils.id([msg.voter, msg.timestamp, signature].join())
this.pollId = msg.pollId
this.voter = msg.voter
@ -73,31 +75,35 @@ export class TimedPollVoteMsg {
this.answer = msg.answer
this.tokenAmount = msg.tokenAmount
this.signature = signature
this.chainId = chainId
}
static async _createWithSignFunction(
signFunction: (
msg: any,
params: string[],
Class: new (sig: string, msg: any) => TimedPollVoteMsg
) => Promise<TimedPollVoteMsg | undefined>,
signFunction: (params: string[]) => Promise<string | undefined>,
signer: JsonRpcSigner | Wallet,
pollId: string,
answer: number,
chainId: number,
tokenAmount?: BigNumber
): Promise<TimedPollVoteMsg | undefined> {
const voter = await signer.getAddress()
const msg = { pollId, voter, timestamp: Date.now(), answer, tokenAmount }
const params = [msg.voter, JSON.stringify(createSignMsgParams(msg))]
return signFunction(msg, params, TimedPollVoteMsg)
const params = [msg.voter, JSON.stringify(createSignMsgParams(msg, chainId))]
const signature = await signFunction(params)
if (signature) {
return new TimedPollVoteMsg(signature, msg, chainId)
} else {
return undefined
}
}
static async create(
signer: JsonRpcSigner | Wallet,
pollId: string,
answer: number,
chainId: number,
tokenAmount?: BigNumber
): Promise<TimedPollVoteMsg | undefined> {
return this._createWithSignFunction(createSignedMsg(signer), signer, pollId, answer, tokenAmount)
return this._createWithSignFunction(createSignFunction(signer), signer, pollId, answer, chainId, tokenAmount)
}
encode() {
@ -119,6 +125,7 @@ export class TimedPollVoteMsg {
static decode(
rawPayload: Uint8Array | undefined,
timestamp: Date | undefined,
chainId: number,
verifyFunction?: (params: any, address: string) => boolean
) {
try {
@ -137,13 +144,13 @@ export class TimedPollVoteMsg {
}
const params = {
data: createSignMsgParams(msg),
data: createSignMsgParams(msg, chainId),
sig: signature,
}
if (verifyFunction ? !verifyFunction : !verifySignature(params, msg.voter)) {
return undefined
}
return new TimedPollVoteMsg(signature, msg)
return new TimedPollVoteMsg(signature, msg, chainId)
} catch {
return undefined
}

View File

@ -1,13 +1,13 @@
import { JsonRpcSigner } from '@ethersproject/providers'
import { Wallet } from 'ethers'
export function createSignedMsg(signer: JsonRpcSigner | Wallet) {
return async <T>(msg: any, params: string[], Class: new (sig: string, msg: any) => T): Promise<T | undefined> => {
export function createSignFunction(signer: JsonRpcSigner | Wallet) {
return async (params: string[]) => {
if ('send' in signer.provider) {
try {
const signature = await signer.provider.send('eth_signTypedData_v4', params)
if (signature) {
return new Class(signature, msg)
return signature
}
} catch {
return undefined

View File

@ -1,10 +1,11 @@
import { MockProvider } from '@ethereum-waffle/provider'
import { expect } from 'chai'
import { Waku } from 'js-waku'
import { WakuVoting } from '../src'
describe('WakuVoting', () => {
it('success', async () => {
const wakuVoting = await WakuVoting.create('test', '0x0', {} as unknown as Waku)
const wakuVoting = await WakuVoting.create('test', '0x0', new MockProvider(), {} as unknown as Waku)
expect(wakuVoting).to.not.be.undefined
})

View File

@ -10,11 +10,12 @@ describe('PollInitMsg', () => {
describe('create', () => {
it('success', async () => {
const poll = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'test',
['one', 'two', 'three'],
PollType.WEIGHTED
PollType.WEIGHTED,
0
)
expect(poll).to.not.be.undefined
@ -32,11 +33,12 @@ describe('PollInitMsg', () => {
it('success NON_WEIGHTED', async () => {
const poll = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'test',
['one', 'two', 'three'],
PollType.NON_WEIGHTED,
0,
BigNumber.from(123)
)
expect(poll).to.not.be.undefined
@ -48,11 +50,12 @@ describe('PollInitMsg', () => {
it('NON_WEIGHTED no minToken', async () => {
const poll = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'test',
['one', 'two', 'three'],
PollType.NON_WEIGHTED
PollType.NON_WEIGHTED,
0
)
expect(poll?.minToken?.toNumber()).to.eq(1)
@ -64,11 +67,12 @@ describe('PollInitMsg', () => {
it('specific end time', async () => {
const poll = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'test',
['one', 'two', 'three'],
PollType.NON_WEIGHTED,
0,
undefined,
100
)
@ -79,33 +83,35 @@ describe('PollInitMsg', () => {
describe('decode/encode', () => {
it('success', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.WEIGHTED
PollType.WEIGHTED,
0
)
expect(data).to.not.be.undefined
if (data) {
const payload = data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data)
expect(PollInitMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq(data)
}
}
})
it('random decode', async () => {
expect(PollInitMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be.undefined
expect(PollInitMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10), 0)).to.be.undefined
})
it('NON_WEIGHTED init', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.NON_WEIGHTED,
0,
BigNumber.from(10)
)
expect(data).to.not.be.undefined
@ -113,18 +119,19 @@ describe('PollInitMsg', () => {
const payload = data.encode()
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data)
expect(PollInitMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq(data)
}
}
})
it('NON_WEIGHTED no min token', async () => {
const data = await PollInitMsg._createWithSignFunction(
async (e) => new PollInitMsg('0x01', e),
async () => '0x01',
alice,
'whats up',
['ab', 'cd', 'ef'],
PollType.NON_WEIGHTED
PollType.NON_WEIGHTED,
0
)
expect(data).to.not.be.undefined
if (data) {
@ -132,7 +139,7 @@ describe('PollInitMsg', () => {
expect(payload).to.not.be.undefined
if (payload) {
expect(PollInitMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq({
expect(PollInitMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq({
...data,
minToken: BigNumber.from(1),
})

View File

@ -10,12 +10,7 @@ describe('TimedPollVoteMsg', () => {
describe('create', () => {
it('success', async () => {
const vote = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
alice,
pollId,
0
)
const vote = await TimedPollVoteMsg._createWithSignFunction(async () => '0x01', alice, pollId, 0, 0)
if (vote) {
expect(vote.voter).to.eq(alice.address)
@ -28,10 +23,11 @@ describe('TimedPollVoteMsg', () => {
it('success token amount', async () => {
const vote = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
async () => '0x01',
alice,
pollId,
1,
0,
BigNumber.from(100)
)
@ -48,12 +44,7 @@ describe('TimedPollVoteMsg', () => {
describe('decode/encode', () => {
it('success', async () => {
const data = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
alice,
pollId,
0
)
const data = await TimedPollVoteMsg._createWithSignFunction(async () => '0x01', alice, pollId, 0, 0)
expect(data).to.not.be.undefined
if (data) {
@ -61,22 +52,23 @@ describe('TimedPollVoteMsg', () => {
expect(payload).to.not.be.undefined
if (payload) {
expect(await TimedPollVoteMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq(data)
expect(await TimedPollVoteMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq(data)
}
}
})
it('random decode', async () => {
expect(TimedPollVoteMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10))).to.be
expect(TimedPollVoteMsg.decode(new Uint8Array([12, 12, 3, 32, 31, 212, 31, 32, 23]), new Date(10), 0)).to.be
.undefined
})
it('data with token', async () => {
const data = await TimedPollVoteMsg._createWithSignFunction(
async (e) => new TimedPollVoteMsg('0x01', e),
async () => '0x01',
alice,
pollId,
0,
0,
BigNumber.from(120)
)
expect(data).to.not.be.undefined
@ -85,7 +77,7 @@ describe('TimedPollVoteMsg', () => {
expect(payload).to.not.be.undefined
if (payload) {
expect(TimedPollVoteMsg.decode(payload, new Date(data.timestamp), () => true)).to.deep.eq({
expect(TimedPollVoteMsg.decode(payload, new Date(data.timestamp), 0, () => true)).to.deep.eq({
...data,
tokenAmount: BigNumber.from(120),
})

View File

@ -7,7 +7,9 @@
"license": "MIT",
"watch": {
"build": {
"patterns": ["src"],
"patterns": [
"src"
],
"extensions": "ts,tsx",
"runOnChangeOnly": false
}
@ -27,6 +29,7 @@
},
"dependencies": {
"@status-waku-voting/core": "^0.1.0",
"@usedapp/core": "^0.4.7",
"ethers": "^5.4.4",
"react": "^17.0.2",
"styled-components": "^5.3.0"

View File

@ -1,11 +1,28 @@
import { useEffect, useState } from 'react'
import { useEffect, useState, useRef } from 'react'
import { WakuPolling } from '@status-waku-voting/core'
import { useEthers } from '@usedapp/core'
import { Provider } from '@ethersproject/providers'
export function useWakuPolling(appName: string, tokenAddress: string) {
const [wakuPolling, setWakuPolling] = useState<WakuPolling | undefined>(undefined)
const queue = useRef(0)
const queuePos = useRef(0)
const { library } = useEthers()
useEffect(() => {
WakuPolling.create(appName, tokenAddress).then((e) => setWakuPolling(e))
const createNewWaku = async (queuePosition: number) => {
while (queuePosition != queuePos.current) {
await new Promise((r) => setTimeout(r, 1000))
}
wakuPolling?.cleanUp()
const newWakuPoll = await WakuPolling.create(appName, tokenAddress, library as unknown as Provider)
setWakuPolling(newWakuPoll)
queuePos.current++
}
if (library) {
createNewWaku(queue.current++)
}
return () => wakuPolling?.cleanUp()
}, [])
}, [library])
return wakuPolling
}