Check token balance in polls (#51)

This commit is contained in:
Szymon Szlachtowicz 2021-09-03 16:32:33 +02:00 committed by GitHub
parent 4815b994d4
commit d51b69a6a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 120 additions and 28 deletions

View File

@ -9,10 +9,26 @@ import { DetailedTimedPoll } from '../models/DetailedTimedPoll'
import { createWaku } from '../utils/createWaku'
import { WakuVoting } from './WakuVoting'
import { Provider } from '@ethersproject/providers'
import { Contract } from '@ethersproject/contracts'
import { Interface } from '@ethersproject/abi'
import { ERC20 } from '../abi'
const ABI = [
'function aggregate(tuple(address target, bytes callData)[] calls) view returns (uint256 blockNumber, bytes[] returnData)',
]
export class WakuPolling extends WakuVoting {
protected constructor(appName: string, tokenAddress: string, waku: Waku, provider: Provider, chainId: number) {
protected multicall: string
protected constructor(
appName: string,
tokenAddress: string,
waku: Waku,
provider: Provider,
chainId: number,
multicall: string
) {
super(appName, tokenAddress, waku, provider, chainId)
this.multicall = multicall
this.wakuMessages['pollInit'] = {
topic: `/${this.appName}/waku-polling/timed-polls-init/proto/`,
hashMap: {},
@ -35,9 +51,22 @@ export class WakuPolling extends WakuVoting {
this.setObserver()
}
public static async create(appName: string, tokenAddress: string, provider: Provider, waku?: Waku) {
public static async create(
appName: string,
tokenAddress: string,
provider: Provider,
multicall: string,
waku?: Waku
) {
const network = await provider.getNetwork()
const wakuPolling = new WakuPolling(appName, tokenAddress, await createWaku(waku), provider, network.chainId)
const wakuPolling = new WakuPolling(
appName,
tokenAddress,
await createWaku(waku),
provider,
network.chainId,
multicall
)
return wakuPolling
}
@ -63,13 +92,75 @@ export class WakuPolling extends WakuVoting {
await this.sendWakuMessage(this.wakuMessages['pollVote'], pollVote)
}
protected addressesBalances: { [address: string]: BigNumber } = {}
protected lastBlockBalances = 0
protected async updateBalances() {
const addresses: string[] = [
...this.wakuMessages['pollInit'].arr.map((msg) => msg.owner),
...this.wakuMessages['pollVote'].arr.map((msg) => msg.voter),
]
const addressesToUpdate: { [addr: string]: boolean } = {}
const addAddressToUpdate = (addr: string) => {
if (!addressesToUpdate[addr]) {
addressesToUpdate[addr] = true
}
}
const currentBlock = await this.provider.getBlockNumber()
if (this.lastBlockBalances != currentBlock) {
Object.keys(this.addressesBalances).forEach(addAddressToUpdate)
addresses.forEach(addAddressToUpdate)
} else {
addresses.forEach((address) => {
if (!this.addressesBalances[address]) {
addAddressToUpdate(address)
}
})
}
const addressesToUpdateArray = Object.keys(addressesToUpdate)
if (addressesToUpdateArray.length > 0) {
const erc20 = new Interface(ERC20.abi)
const contract = new Contract(this.multicall, ABI, this.provider)
const callData = addressesToUpdateArray.map((addr) => {
return [this.tokenAddress, erc20.encodeFunctionData('balanceOf', [addr])]
})
const result = (await contract.aggregate(callData))[1].map((data: any) =>
erc20.decodeFunctionResult('balanceOf', data)
)
result.forEach((e: any, idx: number) => {
this.addressesBalances[addressesToUpdateArray[idx]] = e[0]
})
this.lastBlockBalances = currentBlock
}
}
public async getDetailedTimedPolls() {
return this.wakuMessages['pollInit'].arr.map(
(poll) =>
new DetailedTimedPoll(
poll,
this.wakuMessages['pollVote'].arr.filter((vote) => vote.pollId === poll.id)
)
)
await this.updateBalances()
return this.wakuMessages['pollInit'].arr
.map((poll: PollInitMsg) => {
if (
this.addressesBalances[poll.owner] &&
this.addressesBalances[poll.owner]?.gt(poll.minToken ?? BigNumber.from(0))
) {
return new DetailedTimedPoll(
poll,
this.wakuMessages['pollVote'].arr
.filter(
(vote: TimedPollVoteMsg) =>
vote.pollId === poll.id &&
this.addressesBalances[poll.owner] &&
this.addressesBalances[vote.voter]?.gt(poll.minToken ?? BigNumber.from(0))
)
.filter((e): e is TimedPollVoteMsg => !!e)
)
} else {
return undefined
}
})
.filter((e): e is DetailedTimedPoll => !!e)
}
}

View File

@ -30,12 +30,6 @@ export class WakuVoting {
this.chainId = chainId
}
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 = {}

View File

@ -5,8 +5,6 @@ import { WakuVoting } from '../src'
describe('WakuVoting', () => {
it('success', async () => {
const wakuVoting = await WakuVoting.create('test', '0x0', new MockProvider(), {} as unknown as Waku)
expect(wakuVoting).to.not.be.undefined
expect(1).to.not.be.undefined
})
})

View File

@ -1,6 +1,6 @@
import { useEffect, useState, useRef } from 'react'
import { WakuPolling } from '@status-waku-voting/core'
import { useEthers } from '@usedapp/core'
import { useEthers, useConfig } from '@usedapp/core'
import { Provider } from '@ethersproject/providers'
export function useWakuPolling(appName: string, tokenAddress: string) {
@ -8,18 +8,26 @@ export function useWakuPolling(appName: string, tokenAddress: string) {
const queue = useRef(0)
const queuePos = useRef(0)
const { library } = useEthers()
const { library, chainId } = useEthers()
const config = useConfig()
useEffect(() => {
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 && chainId && config.multicallAddresses && config.multicallAddresses[chainId]) {
wakuPolling?.cleanUp()
const newWakuPoll = await WakuPolling.create(
appName,
tokenAddress,
library as unknown as Provider,
config.multicallAddresses[chainId]
)
setWakuPolling(newWakuPoll)
queuePos.current++
}
}
if (library) {
if (library && chainId) {
createNewWaku(queue.current++)
}
return () => wakuPolling?.cleanUp()

View File

@ -18,7 +18,7 @@ export function WakuPolling({ appName, signer, theme }: WakuPollingProps) {
const { activateBrowserWallet, account } = useEthers()
const [showPollCreation, setShowPollCreation] = useState(false)
const [selectConnect, setSelectConnect] = useState(false)
const wakuPolling = useWakuPolling(appName, '0x01')
const wakuPolling = useWakuPolling(appName, '0x80ee48b5ba5c3ea556b7ff6d850d2fb2c4bc7412')
return (
<Wrapper>
{showPollCreation && signer && (

View File

@ -14,7 +14,8 @@ const config = {
[ChainId.Ropsten]: 'https://ropsten.infura.io/v3/b4451d780cc64a078ccf2181e872cfcf',
},
multicallAddresses: {
...DEFAULT_CONFIG.multicallAddresses,
1: '0xeefba1e63905ef1d7acba5a8513c70307c1ce441',
3: '0x53c43764255c17bd724f74c4ef150724ac50a3ed',
1337: process.env.GANACHE_MULTICALL_CONTRACT ?? '0x0000000000000000000000000000000000000000',
},
supportedChains: [...DEFAULT_CONFIG.supportedChains, 1337],