Skip to content

Commit

Permalink
Merge pull request #697 from clrfund/fix/recipient-removal
Browse files Browse the repository at this point in the history
Only hide project if the removal request is resolved
  • Loading branch information
yuetloo authored Jul 28, 2023
2 parents 9f3829d + 70347bd commit 3500007
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 59 deletions.
95 changes: 78 additions & 17 deletions subgraph/src/OptimisticRecipientRegistryMapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
RequestSubmitted,
} from '../generated/OptimisticRecipientRegistry/OptimisticRecipientRegistry'

import { Recipient } from '../generated/schema'
import { Recipient, RecipientRegistry } from '../generated/schema'

// It is also possible to access smart contracts from mappings. For
// example, the contract that has emitted the event can be connected to
Expand All @@ -31,20 +31,48 @@ export function handleOwnershipTransferred(event: OwnershipTransferred): void {
}

export function handleRequestResolved(event: RequestResolved): void {
log.info('handleRequestResolved', [])

let recipientRegistryId = event.address.toHexString()
let recipientRegistry = RecipientRegistry.load(recipientRegistryId)
if (!recipientRegistry) {
log.warning(
'handleRequestResolved - ignore unknown recipient registry {} hash {}',
[event.address.toHexString(), event.transaction.hash.toHex()]
)
return
}

log.info('handleRequestResolved', [])
let recipientId = event.params._recipientId.toHexString()
let recipient = new Recipient(recipientId)

recipient.requestType = BigInt.fromI32(event.params._type).toString()
recipient.requester = event.transaction.from.toHexString()
recipient.submissionTime = event.params._timestamp.toString()
recipient.rejected = event.params._rejected
recipient.verified = !event.params._rejected
recipient.recipientRegistry = recipientRegistryId
recipient.recipientIndex = event.params._recipientIndex
recipient.requestResolvedHash = event.transaction.hash
// verified means the request is resolved
recipient.verified = true

if (event.params._rejected) {
// this is a challengeRequest
if (event.params._type == 0) {
// reject add request
recipient.rejected = event.params._rejected
recipient.recipientIndex = event.params._recipientIndex
recipient.requester = event.transaction.from.toHexString()
recipient.submissionTime = event.params._timestamp.toString()
} else {
// reject delete request - revert request type back to 'Add'
// to clear the 'Pending removal' status
recipient.requestType = '0'
}
} else {
// this is a executeRequest
recipient.requestType = BigInt.fromI32(event.params._type).toString()
recipient.recipientIndex = event.params._recipientIndex
// reject the recipient if it was a 'delete recipient request'
recipient.rejected = event.params._rejected
recipient.requester = event.transaction.from.toHexString()
recipient.submissionTime = event.params._timestamp.toString()
}

recipient.save()
}
Expand All @@ -53,20 +81,53 @@ export function handleRequestSubmitted(event: RequestSubmitted): void {
log.info('handleRequestSubmitted', [])
let recipientRegistryId = event.address.toHexString()

//TODO: create RecipientRegistry entity here if it does not exist.
let recipientRegistery = RecipientRegistry.load(recipientRegistryId)
if (!recipientRegistery) {
log.warning(
'handleRequestSubmitted - ignore unknown recipient registry {} hash {}',
[event.address.toHexString(), event.transaction.hash.toHex()]
)
return
}

let recipientId = event.params._recipientId.toHexString()
let recipient = new Recipient(recipientId)

recipient.recipientRegistry = recipientRegistryId
recipient.recipientAddress = event.params._recipient
recipient.requestType = BigInt.fromI32(event.params._type).toString()
recipient.requester = event.transaction.from.toHexString()
recipient.submissionTime = event.params._timestamp.toString()
recipient.deposit = event.transaction.value
recipient.recipientMetadata = event.params._metadata
recipient.verified = false
recipient.requestSubmittedHash = event.transaction.hash
recipient.lastUpdatedAt = event.block.timestamp.toString()

if (event.params._type == 0) {
// add recipient request
recipient.requestType = BigInt.fromI32(event.params._type).toString()
recipient.recipientIndex = null
recipient.recipientAddress = event.params._recipient
recipient.requester = event.transaction.from.toHexString()
recipient.deposit = event.transaction.value
recipient.recipientMetadata = event.params._metadata
recipient.requestSubmittedHash = event.transaction.hash

// reset these fields in case the same recipient was added and removed
// in which case these fields could hold values for previous record
recipient.requestResolvedHash = null
recipient.verified = false
recipient.rejected = false
recipient.createdAt = event.block.timestamp.toString()
} else if (event.params._type == 1) {
// mark the record as pending delete
recipient.requestType = BigInt.fromI32(event.params._type).toString()
recipient.verified = false
log.info('handleRequestSubmitted - delete id {}', [recipientId])
} else {
// don't know how to process this request
recipient.requestType = BigInt.fromI32(event.params._type).toString()
log.warning(
'handleRequestSubmitted - ignore unknown type {} from txHash {}',
[
BigInt.fromI32(event.params._type).toString(),
event.transaction.hash.toString(),
]
)
}

recipient.save()
}
35 changes: 18 additions & 17 deletions vue-app/src/api/projects.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BigNumber, Contract, Signer } from 'ethers'
import type { TransactionResponse } from '@ethersproject/abstract-provider'
import { FundingRound } from './abi'
import { FundingRound, OptimisticRecipientRegistry } from './abi'
import { factory, provider, recipientRegistryType, ipfsGatewayUrl } from './core'

import SimpleRegistry from './recipient-registry-simple'
Expand All @@ -9,6 +9,7 @@ import KlerosRegistry from './recipient-registry-kleros'
import sdk from '@/graphql/sdk'
import { getLeaderboardData } from '@/api/leaderboard'
import type { RecipientApplicationData } from '@/api/types'
import { getEventArg } from '@/utils/contracts'

export interface LeaderboardProject {
id: string // Address or another ID depending on registry implementation
Expand Down Expand Up @@ -164,28 +165,28 @@ export async function getProjectByIndex(
}

/**
* Check if the recipient with the submission hash exists in the subgraph
* Return the recipientId for the given transaction hash
* @param transactionHash recipient submission hash
* @returns true if recipients with the submission hash was found
* @returns recipientId or null for not found
*/
export async function recipientExists(transactionHash: string): Promise<boolean> {
const data = await sdk.GetRecipientBySubmitHash({ transactionHash })
return data.recipients.length > 0
}

/**
* Return the recipient for the given submission hash
* @param transactionHash recipient submission hash
* @returns project or null for not found
*/
export async function getRecipientBySubmitHash(transactionHash: string): Promise<Project | null> {
export async function getRecipientIdByHash(transactionHash: string): Promise<string | null> {
try {
const data = await sdk.GetRecipientBySubmitHash({ transactionHash })
const exists = data.recipients.length > 0
return exists ? OptimisticRegistry.decodeProject(data.recipients[0]) : null
const receipt = await provider.getTransactionReceipt(transactionHash)

// should only have 1 event, just in case, return the first matching event
for (const log of receipt.logs) {
const registry = new Contract(log.address, OptimisticRecipientRegistry, provider)
try {
const recipientId = getEventArg(receipt, registry, 'RequestSubmitted', '_recipientId')
return recipientId
} catch {
// try next log
}
}
} catch {
return null
}
return null
}

export function toLeaderboardProject(project: any): LeaderboardProject {
Expand Down
35 changes: 25 additions & 10 deletions vue-app/src/api/recipient-registry-optimistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum RequestStatus {
Accepted = 'Accepted',
Rejected = 'Rejected',
Executed = 'Live',
PendingRemoval = 'Pending removal',
Removed = 'Removed',
}

Expand Down Expand Up @@ -131,6 +132,10 @@ export async function getRequests(registryInfo: RegistryInfo, registryAddress: s

if (recipient.verified) {
request.status = requestType === RequestTypeCode.Removal ? RequestStatus.Removed : RequestStatus.Executed
} else {
if (requestType === RequestTypeCode.Removal) {
request.status = RequestStatus.PendingRemoval
}
}

// In case there are two requests submissions events, we always prioritize
Expand Down Expand Up @@ -245,14 +250,19 @@ export async function getProjects(registryAddress: string, startTime?: number, e
}

if (requestType === RequestTypeCode.Removal) {
const removedAt = submissionTime
if (!startTime || removedAt <= startTime) {
// Start time not specified
// or recipient had been removed before start time
project.isHidden = true
if (recipient.verified) {
const removedAt = submissionTime
if (!startTime || removedAt <= startTime) {
// Start time not specified
// or recipient had been removed before start time
project.isHidden = true
} else {
// Disallow contributions to removed recipient, but don't hide it
project.isLocked = true
}
} else {
// Disallow contributions to removed recipient, but don't hide it
project.isLocked = true
// project is not removed yet, keep the index so that it can still receive contributions
project.index = recipient.recipientIndex
}
}

Expand Down Expand Up @@ -307,9 +317,14 @@ export async function getProject(recipientId: string, filter = true): Promise<Pr
}
}

if (requestType === RequestTypeCode.Removal && recipient.verified) {
// Disallow contributions to removed recipient
project.isLocked = true
if (requestType === RequestTypeCode.Removal) {
if (recipient.verified) {
// Disallow contributions to removed recipient
project.isLocked = true
} else {
// project is not removed yet, keep the index so that it can still receive contributions
project.index = recipient.recipientIndex
}
}
return project
}
Expand Down
17 changes: 17 additions & 0 deletions vue-app/src/api/subgraph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { TransactionReceipt } from '@ethersproject/abstract-provider'
import sdk from '@/graphql/sdk'

/**
* Check if the transaction in the receipt exists in the subgraph
* @param receipt transaction receipt
* @returns true if the latest block number in subgraph is greater than the transaction block number
*/
export async function isTransactionInSubgraph(receipt: TransactionReceipt): Promise<boolean> {
const data = await sdk.GetLatestBlockNumber()

if (!data._meta?.block.number) {
return false
}

return data._meta.block.number > receipt.blockNumber
}
4 changes: 2 additions & 2 deletions vue-app/src/utils/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ export async function waitForTransaction(
*/
export async function waitForTransactionAndCheck(
pendingTransaction: Promise<TransactionResponse>,
checkFn: (hash: string) => Promise<boolean>,
checkFn: (receipt: TransactionReceipt) => Promise<boolean>,
onTransactionHash?: (hash: string) => void,
): Promise<TransactionReceipt> {
const receipt = await waitForTransaction(pendingTransaction, onTransactionHash)

return new Promise(resolve => {
async function checkAndWait(depth = 0) {
if (await checkFn(receipt.transactionHash)) {
if (await checkFn(receipt)) {
resolve(receipt)
} else {
if (depth > MAX_WAIT_DEPTH) {
Expand Down
7 changes: 4 additions & 3 deletions vue-app/src/views/JoinView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,8 @@ import { useVuelidate } from '@vuelidate/core'
import { required, requiredIf, email, maxLength, url, helpers } from '@vuelidate/validators'
import type { RecipientApplicationData } from '@/api/types'
import type { Project } from '@/api/projects'
import { recipientExists, formToProjectInterface } from '@/api/projects'
import { isTransactionInSubgraph } from '@/api/subgraph'
import { formToProjectInterface } from '@/api/projects'
import { chain, showComplianceRequirement, isOptimisticRecipientRegistry } from '@/api/core'
import { DateTime } from 'luxon'
import { useRecipientStore, useAppStore, useUserStore } from '@/stores'
Expand Down Expand Up @@ -948,8 +949,8 @@ async function addRecipient() {
recipientRegistryInfo.value.deposit,
currentUser.value.walletProvider.getSigner(),
),
hash => {
return isOptimisticRecipientRegistry ? recipientExists(hash) : Promise.resolve(true)
receipt => {
return isOptimisticRecipientRegistry ? isTransactionInSubgraph(receipt) : Promise.resolve(true)
},
hash => (txHash.value = hash),
)
Expand Down
8 changes: 4 additions & 4 deletions vue-app/src/views/ProjectAdded.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import { useAppStore } from '@/stores'
import { useRoute } from 'vue-router'
import { storeToRefs } from 'pinia'
import { isOptimisticRecipientRegistry } from '@/api/core'
import { getRecipientBySubmitHash } from '@/api/projects'
import { getRecipientIdByHash } from '@/api/projects'
const route = useRoute()
const appStore = useAppStore()
Expand All @@ -59,9 +59,9 @@ const hash = computed(() => route.params.hash as string)
const recipientId = ref('')
onMounted(async () => {
const recipient = await getRecipientBySubmitHash(hash.value)
if (recipient) {
recipientId.value = recipient.id
const id = await getRecipientIdByHash(hash.value)
if (id) {
recipientId.value = id
}
})
</script>
Expand Down
12 changes: 6 additions & 6 deletions vue-app/src/views/RecipientRegistry.vue
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,7 @@
>
<img src="@/assets/checkmark.svg" />
</div>
<div
class="icon-btn-reject"
v-if="isOwner && (isPending(request) || isAccepted(request))"
@click="reject(request)"
>
<div class="icon-btn-reject" v-if="isOwner && isPending(request)" @click="reject(request)">
<img src="@/assets/close.svg" />
</div>
</div>
Expand Down Expand Up @@ -196,8 +192,12 @@ function isExecuted(request: Request): boolean {
return request.status === RequestStatus.Executed
}
function isPendingRemoval(request: Request): boolean {
return request.status === RequestStatus.PendingRemoval
}
function hasProjectLink(request: Request): boolean {
return request.type === RequestType.Registration && request.status === RequestStatus.Executed
return isExecuted(request) || isPendingRemoval(request)
}
async function approve(request: Request): Promise<void> {
Expand Down

0 comments on commit 3500007

Please sign in to comment.