mirror of
https://github.com/status-im/community-dapp.git
synced 2025-02-23 03:28:21 +00:00
Use EIP712 for feature (#188)
This commit is contained in:
parent
343cd57fee
commit
5c38980d4b
@ -33,8 +33,8 @@ function getTop(map: CommunitiesFeatureVotes, top: number) {
|
||||
.slice(0, top)
|
||||
}
|
||||
|
||||
export async function receiveWakuFeature(waku: Waku | undefined, topic: string) {
|
||||
let messages = await receiveWakuFeatureMsg(waku, topic)
|
||||
export async function receiveWakuFeature(waku: Waku | undefined, topic: string, chainId: number) {
|
||||
let messages = await receiveWakuFeatureMsg(waku, topic, chainId)
|
||||
const wakuFeatured: CommunitiesFeatureVotes = {}
|
||||
let top5: [string, CommunityFeatureVotes][] = []
|
||||
if (messages && messages?.length > 0) {
|
||||
|
@ -1,22 +1,56 @@
|
||||
import { Waku, WakuMessage } from 'js-waku'
|
||||
import { WakuFeatureData } from '../models/waku'
|
||||
import { packAndArrayify } from './ethMessage'
|
||||
import { recoverAddress } from './ethMessage'
|
||||
import { utils, BigNumber } from 'ethers'
|
||||
import { JsonRpcSigner } from '@ethersproject/providers'
|
||||
import proto from './loadProtons'
|
||||
import { TypedFeature } from '../models/TypedData'
|
||||
|
||||
function verifyWakuFeatureMsg(data: WakuFeatureData | undefined): data is WakuFeatureData {
|
||||
function createTypedData(chainId: number, voter: string, sntAmount: BigNumber, publicKey: string, timestamp: Date) {
|
||||
return {
|
||||
types: {
|
||||
EIP712Domain: [
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'version', type: 'string' },
|
||||
{ name: 'chainId', type: 'uint256' },
|
||||
],
|
||||
Feature: [
|
||||
{ name: 'voter', type: 'address' },
|
||||
{ name: 'sntAmount', type: 'uint256' },
|
||||
{ name: 'publicKey', type: 'bytes' },
|
||||
{ name: 'timestamp', type: 'uint64' },
|
||||
{ name: 'chainId', type: 'uint64' },
|
||||
],
|
||||
},
|
||||
primaryType: 'Feature',
|
||||
domain: {
|
||||
name: 'Featuring vote',
|
||||
version: '1',
|
||||
chainId: chainId,
|
||||
},
|
||||
message: {
|
||||
voter: voter,
|
||||
sntAmount: sntAmount.toHexString(),
|
||||
publicKey: publicKey,
|
||||
timestamp: timestamp.getTime(),
|
||||
},
|
||||
} as TypedFeature
|
||||
}
|
||||
|
||||
function verifyWakuFeatureMsg(data: WakuFeatureData | undefined, chainId: number): data is WakuFeatureData {
|
||||
if (!data) {
|
||||
return false
|
||||
}
|
||||
const types = ['address', 'uint256', 'address', 'uint256']
|
||||
const message = [data.voter, data.sntAmount, data.publicKey, data.timestamp.getTime()]
|
||||
const verifiedAddress = utils.verifyMessage(packAndArrayify(types, message), data.sign)
|
||||
|
||||
if (data.msgTimestamp?.getTime() != data.timestamp.getTime() || verifiedAddress != data.voter) {
|
||||
const typedData = createTypedData(chainId, data.voter, data.sntAmount, data.publicKey, data.timestamp)
|
||||
try {
|
||||
const verifiedAddress = utils.getAddress(recoverAddress(typedData, data.sign))
|
||||
if (data.msgTimestamp?.getTime() != data.timestamp.getTime() || verifiedAddress != data.voter) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
function decodeWakuFeature(msg: WakuMessage): WakuFeatureData | undefined {
|
||||
@ -40,14 +74,14 @@ function decodeWakuFeature(msg: WakuMessage): WakuFeatureData | undefined {
|
||||
}
|
||||
}
|
||||
|
||||
export function decodeWakuFeatures(messages: WakuMessage[] | null) {
|
||||
return messages?.map(decodeWakuFeature).filter(verifyWakuFeatureMsg)
|
||||
export function decodeWakuFeatures(messages: WakuMessage[] | null, chainId: number) {
|
||||
return messages?.map(decodeWakuFeature).filter((e): e is WakuFeatureData => verifyWakuFeatureMsg(e, chainId))
|
||||
}
|
||||
|
||||
export async function receiveWakuFeatureMsg(waku: Waku | undefined, topic: string) {
|
||||
export async function receiveWakuFeatureMsg(waku: Waku | undefined, topic: string, chainId: number) {
|
||||
if (waku) {
|
||||
const messages = await waku.store.queryHistory({ contentTopics: [topic] })
|
||||
return decodeWakuFeatures(messages)
|
||||
return decodeWakuFeatures(messages, chainId)
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,29 +90,30 @@ export async function createWakuFeatureMsg(
|
||||
signer: JsonRpcSigner | undefined,
|
||||
sntAmount: BigNumber,
|
||||
publicKey: string,
|
||||
contentTopic: string
|
||||
contentTopic: string,
|
||||
chainId: number,
|
||||
sig?: string,
|
||||
time?: Date
|
||||
) {
|
||||
if (!account || !signer) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const provider = signer.provider
|
||||
const signerAddress = await signer?.getAddress()
|
||||
if (signerAddress != account) {
|
||||
return undefined
|
||||
}
|
||||
const timestamp = new Date()
|
||||
const timestamp = time ? time : new Date()
|
||||
const data = createTypedData(chainId, account, sntAmount, publicKey, timestamp)
|
||||
const signature = sig ? sig : await provider?.send('eth_signTypedData_v3', [account, JSON.stringify(data)])
|
||||
|
||||
const types = ['address', 'uint256', 'address', 'uint256']
|
||||
const message = [account, sntAmount, publicKey, BigNumber.from(timestamp.getTime())]
|
||||
const sign = await signer.signMessage(packAndArrayify(types, message))
|
||||
|
||||
if (sign) {
|
||||
if (signature) {
|
||||
const payload = proto.WakuFeature.encode({
|
||||
voter: account,
|
||||
sntAmount: utils.arrayify(sntAmount),
|
||||
publicKey,
|
||||
timestamp,
|
||||
sign,
|
||||
sign: signature,
|
||||
})
|
||||
|
||||
const msg = WakuMessage.fromBytes(payload, {
|
||||
|
@ -7,17 +7,21 @@ import { BigNumber } from 'ethers'
|
||||
|
||||
export function useSendWakuFeature() {
|
||||
const { waku } = useWaku()
|
||||
const { account, library } = useEthers()
|
||||
const { account, library, chainId } = useEthers()
|
||||
const { config } = useConfig()
|
||||
|
||||
const sendWakuFeature = useCallback(
|
||||
async (voteAmount: number, publicKey: string) => {
|
||||
if (!chainId) {
|
||||
return
|
||||
}
|
||||
const msg = await createWakuFeatureMsg(
|
||||
account,
|
||||
library?.getSigner(),
|
||||
BigNumber.from(voteAmount),
|
||||
publicKey,
|
||||
config.wakuFeatureTopic
|
||||
config.wakuFeatureTopic,
|
||||
chainId
|
||||
)
|
||||
if (msg) {
|
||||
if (waku) {
|
||||
@ -27,7 +31,7 @@ export function useSendWakuFeature() {
|
||||
}
|
||||
}
|
||||
},
|
||||
[waku, library, account]
|
||||
[waku, library, account, chainId]
|
||||
)
|
||||
|
||||
return sendWakuFeature
|
||||
|
@ -12,3 +12,18 @@ export interface TypedVote extends TypedMessage<VoteType> {
|
||||
voter: string
|
||||
}
|
||||
}
|
||||
|
||||
type FeatureType = {
|
||||
EIP712Domain: { name: string; type: string }[]
|
||||
Feature: { name: string; type: string }[]
|
||||
}
|
||||
|
||||
export interface TypedFeature extends TypedMessage<FeatureType> {
|
||||
message: {
|
||||
voter: string
|
||||
sntAmount: string
|
||||
publicKey: string
|
||||
timestamp: number
|
||||
chainId: number
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useEthers } from '@usedapp/core'
|
||||
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
||||
import { receiveWakuFeature } from '../../helpers/receiveWakuFeature'
|
||||
import { useConfig } from '../config'
|
||||
@ -24,16 +25,19 @@ export function WakuFeatureProvider({ children }: WakuFeatureProviderProps) {
|
||||
const [featured, setFeatured] = useState<any[]>([])
|
||||
const { waku } = useWaku()
|
||||
const { config } = useConfig()
|
||||
const { chainId } = useEthers()
|
||||
|
||||
useEffect(() => {
|
||||
const get = async () => {
|
||||
const { wakuFeatured, top5 } = await receiveWakuFeature(waku, config.wakuFeatureTopic)
|
||||
setFeatureVotes(wakuFeatured)
|
||||
setFeatured(top5)
|
||||
if (chainId) {
|
||||
const { wakuFeatured, top5 } = await receiveWakuFeature(waku, config.wakuFeatureTopic, chainId)
|
||||
setFeatureVotes(wakuFeatured)
|
||||
setFeatured(top5)
|
||||
}
|
||||
}
|
||||
get()
|
||||
const task = setInterval(get, 10000)
|
||||
return () => clearInterval(task)
|
||||
}, [waku?.libp2p?.peerId?.toString()])
|
||||
}, [waku?.libp2p?.peerId?.toString(), chainId])
|
||||
return <WakuFeatureContext.Provider value={{ featureVotes, featured }} children={children} />
|
||||
}
|
||||
|
@ -99,28 +99,31 @@ describe('wakuMessage', () => {
|
||||
})
|
||||
const msg = await WakuMessage.fromBytes(payload)
|
||||
const msg2 = await createWakuFeatureMsg(
|
||||
alice.address,
|
||||
'0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff',
|
||||
alice as unknown as JsonRpcSigner,
|
||||
BigNumber.from(123),
|
||||
bob.address,
|
||||
'/test/'
|
||||
BigNumber.from('0x10'),
|
||||
'0x1234',
|
||||
'/test/',
|
||||
1337,
|
||||
'0x4e744a50da20c547a4adf888d2bc411efcf3b833cfc79c461b340e6145c34dc314d7a1a82127fff63a34efec0ff11be48929bb6a020ed30dfdeab8ac8c32fffa1c',
|
||||
new Date(1)
|
||||
)
|
||||
|
||||
expect(msg2).to.not.be.undefined
|
||||
if (msg2) {
|
||||
const response = decodeWakuFeatures([msg, msg2]) ?? []
|
||||
const response = decodeWakuFeatures([msg, msg2], 1337) ?? []
|
||||
|
||||
expect(response.length).to.eq(1)
|
||||
const data = response[0]
|
||||
expect(data).to.not.be.undefined
|
||||
expect(data?.voter).to.eq(alice.address)
|
||||
expect(data?.publicKey).to.eq(bob.address)
|
||||
expect(data?.sntAmount).to.deep.eq(BigNumber.from(123))
|
||||
expect(data?.voter).to.eq('0x17ec8597ff92C3F44523bDc65BF0f1bE632917ff')
|
||||
expect(data?.publicKey).to.eq('0x1234')
|
||||
expect(data?.sntAmount).to.deep.eq(BigNumber.from('0x10'))
|
||||
}
|
||||
})
|
||||
|
||||
it('empty', async () => {
|
||||
expect(decodeWakuFeatures([])).to.deep.eq([])
|
||||
expect(decodeWakuFeatures([], 1337)).to.deep.eq([])
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -132,12 +132,13 @@ async function fixture([alice, firstAddress, secondAddress]: any[], provider: Mo
|
||||
return { contract, directory, alice, firstAddress, secondAddress, provider }
|
||||
}
|
||||
|
||||
before(async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
typedData.domain.chainId = 1
|
||||
typedData.domain.verifyingContract = contract.address
|
||||
})
|
||||
|
||||
describe('Contract', () => {
|
||||
before(async () => {
|
||||
const { contract } = await loadFixture(fixture)
|
||||
typedData.domain.chainId = 1
|
||||
typedData.domain.verifyingContract = contract.address
|
||||
})
|
||||
it('deploys properly', async () => {
|
||||
const { contract, directory } = await loadFixture(fixture)
|
||||
await expect(await contract.directory()).to.eq(directory.address)
|
||||
|
Loading…
x
Reference in New Issue
Block a user