Skip to content

Commit

Permalink
feat: implement request-resolved handler
Browse files Browse the repository at this point in the history
  • Loading branch information
eccentricexit committed Apr 15, 2020
1 parent 70b5a99 commit 4ca2aa6
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 42 deletions.
15 changes: 12 additions & 3 deletions src/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@ import { ethers } from 'ethers'
import requestSubmittedHandler from './request-submitted'
import requestResolvedHandler from './request-resolved'

export default async function addTCRListeners(tcr: ethers.Contract) {
export default async function addTCRListeners(
tcr: ethers.Contract,
batchWithdraw: ethers.Contract,
intervals: BlockInterval[],
provider: ethers.providers.Provider
) {
// Submissions and removal requests.
tcr.on(
tcr.filters.RequestSubmitted(),
requestSubmittedHandler()
)

// Request resolved.
// TODO: Filter out unresolved item status change events.
tcr.on(
tcr.filters.ItemStatusChange(),
requestResolvedHandler()
requestResolvedHandler(
tcr,
batchWithdraw,
intervals,
provider
)
)

console.info(`Done setting up listeners for ${tcr.address}`)
Expand Down
35 changes: 31 additions & 4 deletions src/handlers/request-resolved.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
export default () => async () => {
// TODO: Remove item from watch list.
// TODO: Withdraw pending crowdfunding rewards.
}
import withdrawRewards from "../utils/withdraw-rewards"
import { BigNumber } from "ethers/utils"
import { ethers } from "ethers"

/**
* Builds a handler for request resolved events (or rather, ItemStatusChange events with resolved value set to true.)
*/
export default (
tcr: ethers.Contract,
batchWithdraw: ethers.Contract,
intervals: BlockInterval[],
provider: ethers.providers.Provider
) => async (
_itemID: string,
_requestIndex: BigNumber,
_roundIndex: BigNumber,
_disputed: boolean,
_resolved: boolean
) => {
if (!_resolved) return
await withdrawRewards(
_itemID,
_requestIndex,
tcr,
batchWithdraw,
intervals,
provider
)

// TODO: Remove item from watch list.
}
53 changes: 19 additions & 34 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dotenv.config({ path: ".env" })
// Run env variable checks.
import './utils/env-check'
import { bigNumberify } from 'ethers/utils'
import withdrawRewards from './utils/withdraw-rewards'

const provider = new ethers.providers.JsonRpcProvider(process.env.PROVIDER_URL)
provider.pollingInterval = 60 * 1000 // Poll every minute.
Expand Down Expand Up @@ -76,10 +77,19 @@ const deploymentBlock = Number(process.env.FACTORY_BLOCK_NUM) || 0

// Add listeners for events emitted by the TCRs and
// do the same for new TCRs created while the bot is running.
await Promise.all(tcrs.map(tcr => addTCRListeners(tcr)))
await Promise.all(tcrs.map(tcr => addTCRListeners(
tcr,
batchWithdraw,
intervals,
provider
)))

gtcrFactory.on(gtcrFactory.filters.NewGTCR(), _address =>
addTCRListeners(
new ethers.Contract(_address, _GeneralizedTCR.abi, signer),
batchWithdraw,
intervals,
provider
)
)

Expand Down Expand Up @@ -157,39 +167,14 @@ const deploymentBlock = Number(process.env.FACTORY_BLOCK_NUM) || 0
const resolvedRequests = requestSubmittedEvents
.filter(e => !pendingRequests.includes(e))

resolvedRequests.forEach(async ({ values: {_itemID, _requestID }}) => {
const { disputed } = await tcr.getRequestInfo(_itemID, _requestID)
if (!disputed) return // No rewards to withdraw if there was never a dispute.

const contributionEvents = (await Promise.all(
intervals.map(async interval => provider.getLogs({
...tcr.filters.AppealContribution(_itemID, null, _requestID),
}))
))
.reduce((acc, curr) => [...acc, ...curr])
.map(rawEvent => tcr.interface.parseLog(rawEvent))
.filter(({ values: { _round }}) => _round.toNumber() !== 0) // Ignore first round

// A new AppealContribution event is emmited every time
// someone makes a contribution.
// Since batchRoundWithdraw() withdraws all contributions from
// every round by a contributor, we avoid withdrawing
// for the same contributor more than once by using a set.
const done = new Set()
contributionEvents.forEach(async ({ values: { _contributor, _itemID, _request }}) => {
if (done.has(_contributor))
await batchWithdraw.batchRoundWithdraw(
tcr.address,
_contributor,
_itemID,
_request,
0,
0
)

done.add(_contributor)
})
})
resolvedRequests.forEach(({ values: {_itemID, _requestID }}) => withdrawRewards(
_itemID,
_requestID,
tcr,
batchWithdraw,
intervals,
provider
))
})

// TODO: Fetch requests in the watchlist every X minutes.
Expand Down
4 changes: 4 additions & 0 deletions src/types/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
interface BlockInterval {
fromBlock: number
toBlock: number
}
6 changes: 5 additions & 1 deletion src/utils/get-intervals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
* @param currentBlock The current height of the blockchain.
* @param blocksPerRequest The number of blocks to scan per request.
*/
export default function (fromBlock: number, currentBlock: number, blocksPerRequest: number = 1000000) {
export default function (
fromBlock: number,
currentBlock: number,
blocksPerRequest: number = 1000000
) : BlockInterval[] {
// Fetching event logs in a single request can (this was happening) cause
// the provider to timeout the request.
// To get around this we can split it into multiple, smaller requests.
Expand Down
43 changes: 43 additions & 0 deletions src/utils/withdraw-rewards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ethers } from "ethers"
import { BigNumber } from "ethers/utils"

export default async function withdrawRewards(
itemID: string,
requestID: BigNumber,
tcr: ethers.Contract,
batchWithdraw: ethers.Contract,
blockIntervals: BlockInterval[],
provider: ethers.providers.Provider
) {
const { disputed } = await tcr.getRequestInfo(itemID, requestID)
if (!disputed) return // No rewards to withdraw if there was never a dispute.

const contributionEvents = (await Promise.all(
blockIntervals.map(async interval => provider.getLogs({
...tcr.filters.AppealContribution(itemID, null, requestID),
}))
))
.reduce((acc, curr) => [...acc, ...curr])
.map(rawEvent => tcr.interface.parseLog(rawEvent))
.filter(({ values: { _round } }) => _round.toNumber() !== 0) // Ignore first round

// A new AppealContribution event is emmited every time
// someone makes a contribution.
// Since batchRoundWithdraw() withdraws all contributions from
// every round by a contributor, we avoid withdrawing
// for the same contributor more than once by using a set.
const done = new Set()
contributionEvents.forEach(async ({ values: { _contributor, itemID, _request } }) => {
if (done.has(_contributor))
await batchWithdraw.batchRoundWithdraw(
tcr.address,
_contributor,
itemID,
_request,
0,
0
)

done.add(_contributor)
})
}

0 comments on commit 4ca2aa6

Please sign in to comment.