Skip to content

Commit

Permalink
chore: proposal projects deprecation (#1890)
Browse files Browse the repository at this point in the history
* chore: updated projects endpoint

* refactor: fine tune projects list query, add cache to all vestings from subgraph, cache project list

* refactor: legacy condition

* chore: use proposal configuration size or funding for project size (B&T)

* chore: use proposal creation/update dates for project in list, sort by most recent vesting dates

* chore: remove ProposalProject and TransparencyVestings

* chore: new user projects query and type

* fix: lowercase vesting addresses before querying subgraph

* refactor: fn rename
  • Loading branch information
1emu committed Aug 19, 2024
1 parent a1dc8e1 commit 2b83ba6
Show file tree
Hide file tree
Showing 13 changed files with 306 additions and 283 deletions.
25 changes: 0 additions & 25 deletions src/clients/Transparency.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { VestingStatus } from '../entities/Grant/types'
import { TokenInWallet } from '../entities/Transparency/types'
import { ErrorCategory } from '../utils/errorCategories'

Expand Down Expand Up @@ -53,20 +52,6 @@ export type TransparencyBudget = {
category_percentages: Record<string, number>
}

export type TransparencyVesting = {
proposal_id: string
token: string
vesting_address: string
vesting_released: number
vesting_releasable: number
vesting_start_at: string
vesting_finish_at: string
vesting_contract_token_balance: number
vesting_total_amount: number
vesting_status: VestingStatus
duration_in_months: number
}

const EMPTY_API: TransparencyData = {
balances: [],
income: {
Expand Down Expand Up @@ -108,14 +93,4 @@ export class Transparency {
return []
}
}

static async getVestings() {
try {
const response = (await (await fetch(`${API_URL}/vestings.json`)).json()) as TransparencyVesting[]
return response
} catch (error) {
ErrorClient.report('Failed to fetch transparency vestings data', { error, category: ErrorCategory.Transparency })
return []
}
}
}
4 changes: 3 additions & 1 deletion src/clients/VestingData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ export function getTokenSymbolFromAddress(tokenAddress: string) {
return 'USDC'
case '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2':
return 'WETH'
default:
console.log(`Unable to parse token contract address: ${tokenAddress}`)
return 'ETH'
}
throw new Error(`Unable to parse token contract address: ${tokenAddress}`)
}
22 changes: 15 additions & 7 deletions src/clients/VestingsSubgraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { VESTINGS_QUERY_ENDPOINT } from '../entities/Snapshot/constants'
import { SubgraphVesting } from './VestingSubgraphTypes'
import { trimLastForwardSlash } from './utils'

const OLDEST_INDEXED_BLOCK = 20463272

export class VestingsSubgraph {
static Cache = new Map<string, VestingsSubgraph>()
private readonly queryEndpoint: string
Expand Down Expand Up @@ -72,7 +74,7 @@ export class VestingsSubgraph {
}
`

const variables = { address }
const variables = { address: address.toLowerCase() }
const response = await fetch(this.queryEndpoint, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
Expand All @@ -86,10 +88,15 @@ export class VestingsSubgraph {
return body?.data?.vestings[0] || {}
}

async getVestings(addresses: string[]): Promise<SubgraphVesting[]> {
async getVestings(addresses?: string[]): Promise<SubgraphVesting[]> {
const queryAddresses = addresses && addresses.length > 0
const addressesQuery = queryAddresses
? `where: { id_in: $addresses }`
: 'block: {number_gte: $blockNumber}, first: 1000'
const addressesParam = queryAddresses ? `$addresses: [String]!` : '$blockNumber: Int!'
const query = `
query getVestings($addresses: [String]!) {
vestings(where: { id_in: $addresses }){
query getVestings(${addressesParam}) {
vestings(${addressesQuery}){
id
version
duration
Expand Down Expand Up @@ -122,14 +129,15 @@ export class VestingsSubgraph {
}
}
`

const variables = { addresses }
const variables = queryAddresses
? { addresses: addresses.map((address) => address.toLowerCase()) }
: { blockNumber: OLDEST_INDEXED_BLOCK }
const response = await fetch(this.queryEndpoint, {
method: 'post',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
query,
variables: variables,
variables,
}),
})

Expand Down
26 changes: 0 additions & 26 deletions src/entities/Proposal/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,32 +469,6 @@ export default class ProposalModel extends Model<ProposalAttributes> {
return proposals.map(this.parse)
}

static async getProjectList(): Promise<ProposalWithProject[]> {
const status = [ProposalStatus.Passed, ProposalStatus.Enacted].map((status) => SQL`${status}`)
const types = [ProposalType.Bid, ProposalType.Grant].map((type) => SQL`${type}`)

const proposals = await this.namedQuery(
'get_project_list',
SQL`
SELECT prop.*,
proj.id as project_id,
COALESCE(json_agg(DISTINCT to_jsonb(pe.*)) FILTER (WHERE pe.id IS NOT NULL), '[]') as personnel,
COALESCE(array_agg(co.address) FILTER (WHERE co.address IS NOT NULL), '{}') AS coauthors
FROM ${table(ProposalModel)} prop
LEFT OUTER JOIN ${table(ProjectModel)} proj on prop.id = proj.proposal_id
LEFT JOIN ${table(PersonnelModel)} pe ON proj.id = pe.project_id AND pe.deleted = false
LEFT JOIN ${table(CoauthorModel)} co ON prop.id = co.proposal_id AND co.status = ${CoauthorStatus.APPROVED}
WHERE prop."deleted" = FALSE
AND prop."type" IN (${join(types)})
AND prop."status" IN (${join(status)})
GROUP BY prop.id, proj.id
ORDER BY prop."created_at" DESC
`
)

return proposals.map(this.parseProposalWithProject)
}

private static parseTimeframe(timeFrame?: string | null) {
const date = Time.utc()
switch (timeFrame) {
Expand Down
22 changes: 1 addition & 21 deletions src/entities/Proposal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,27 +803,7 @@ export type ProjectFunding = {
vesting?: Vesting
}

export type ProposalProject = {
id: string
project_id?: string | null
status: ProjectStatus
title: string
user: string
coAuthors?: string[]
personnel: PersonnelAttributes[]
size: number
type: ProposalType
about: string
created_at: number
updated_at: number
configuration: {
category: ProposalGrantCategory
tier: string
}
funding?: ProjectFunding
}

export type ProposalProjectWithUpdate = ProposalProject & {
export type LatestUpdate = {
update?: IndexedUpdate | null
update_timestamp?: number
}
Expand Down
3 changes: 0 additions & 3 deletions src/entities/Proposal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ export const SITEMAP_ITEMS_PER_PAGE = 100
export const DEFAULT_CHOICES = ['yes', 'no', 'abstain']
export const REGEX_NAME = new RegExp(`^([a-zA-Z0-9]){${MIN_NAME_SIZE},${MAX_NAME_SIZE}}$`)

//TODO: avoid manually calculating cliff, use subgraph or contract method instead
export const CLIFF_PERIOD_IN_DAYS = 29

export function formatBalance(value: number | bigint) {
return numeral(value).format('0,0')
}
Expand Down
2 changes: 1 addition & 1 deletion src/entities/Updates/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export type UpdateAttributes = Partial<UpdateGeneralSection> &
discourse_topic_slug?: string
}

export type IndexedUpdate = UpdateAttributes & {
export type IndexedUpdate = Partial<UpdateAttributes> & {
index: number
}

Expand Down
122 changes: 120 additions & 2 deletions src/models/Project.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Model } from 'decentraland-gatsby/dist/entities/Database/model'
import { SQL, table } from 'decentraland-gatsby/dist/entities/Database/utils'
import { SQL, conditional, table } from 'decentraland-gatsby/dist/entities/Database/utils'
import isEthereumAddress from 'validator/lib/isEthereumAddress'
import isUUID from 'validator/lib/isUUID'

import CoauthorModel from '../entities/Coauthor/model'
import { CoauthorStatus } from '../entities/Coauthor/types'
import { ProjectStatus } from '../entities/Grant/types'
import ProposalModel from '../entities/Proposal/model'
import { ProjectFunding } from '../entities/Proposal/types'
import { LatestUpdate, ProjectFunding, ProposalAttributes, ProposalType } from '../entities/Proposal/types'
import UpdateModel from '../entities/Updates/model'
import { UpdateAttributes } from '../entities/Updates/types'

import PersonnelModel, { PersonnelAttributes } from './Personnel'
import ProjectLinkModel, { ProjectLink } from './ProjectLink'
Expand Down Expand Up @@ -35,6 +37,30 @@ export type Project = ProjectAttributes & {
funding?: ProjectFunding
}

export type ProjectInList = Pick<Project, 'id' | 'proposal_id' | 'status' | 'title' | 'author' | 'funding'> &
Pick<ProposalAttributes, 'type' | 'configuration'> & {
latest_update?: LatestUpdate
created_at: number
updated_at: number
}

type ProposalDataForProject = Pick<
ProposalAttributes,
'enacting_tx' | 'enacted_description' | 'vesting_addresses' | 'type' | 'configuration'
> & {
proposal_created_at: Date
proposal_updated_at: Date
}

export type ProjectQueryResult = Pick<Project, 'id' | 'proposal_id' | 'status' | 'title' | 'author'> &
ProposalDataForProject & { updates?: UpdateAttributes[] }

export type UserProject = Pick<
Project,
'id' | 'proposal_id' | 'status' | 'title' | 'author' | 'personnel' | 'coauthors' | 'funding'
> &
ProposalDataForProject

export default class ProjectModel extends Model<ProjectAttributes> {
static tableName = 'projects'
static withTimestamps = false
Expand Down Expand Up @@ -96,4 +122,96 @@ export default class ProjectModel extends Model<ProjectAttributes> {
const result = await this.namedQuery<{ exists: boolean }>(`is_author_or_coauthor`, query)
return result[0]?.exists || false
}

static async getProjectsWithUpdates(from?: Date, to?: Date): Promise<ProjectQueryResult[]> {
const query = SQL`
SELECT
pr.id,
pr.proposal_id,
pr.status,
pr.title,
p.type,
p.enacting_tx,
p.enacted_description,
p.configuration,
p.user as author,
p.vesting_addresses,
p.created_at as proposal_created_at,
p.updated_at as proposal_updated_at,
COALESCE(json_agg(DISTINCT to_jsonb(ordered_updates.*)) FILTER (WHERE ordered_updates.id IS NOT NULL), '[]') as updates
FROM ${table(ProjectModel)} pr
JOIN ${table(ProposalModel)} p ON pr.proposal_id = p.id
LEFT JOIN (SELECT * FROM ${table(UpdateModel)} up ORDER BY up.created_at DESC) ordered_updates
ON pr.id = ordered_updates.project_id
WHERE 1=1
${conditional(!!from, SQL`AND pr.created_at >= ${from}`)}
${conditional(!!to, SQL`AND pr.created_at <= ${to}`)}
GROUP BY
pr.id,
pr.proposal_id,
pr.status,
pr.title,
p.created_at,
p.updated_at,
p.type,
p.enacting_tx,
p.enacted_description,
p.configuration,
p.user,
p.vesting_addresses,
p.updated_at
ORDER BY p.created_at DESC;
`

const result = await this.namedQuery<ProjectQueryResult>(`get_projects`, query)
return result || []
}

static async getUserProjects(userAddress: string): Promise<UserProject[]> {
const query = SQL`
SELECT
pr.id,
pr.proposal_id,
pr.status,
pr.title,
COALESCE(json_agg(DISTINCT to_jsonb(pe.*)) FILTER (WHERE pe.id IS NOT NULL), '[]') as personnel,
COALESCE(array_agg(co.address) FILTER (WHERE co.address IS NOT NULL), '{}') AS coauthors,
p.enacting_tx,
p.enacted_description,
p.vesting_addresses,
p.type,
p.configuration,
p.user as author,
p.created_at as proposal_created_at,
p.updated_at as proposal_updated_at
FROM ${table(ProjectModel)} pr
JOIN ${table(ProposalModel)} p ON pr.proposal_id = p.id
LEFT JOIN ${table(PersonnelModel)} pe ON pr.id = pe.project_id AND pe.deleted = false
LEFT JOIN ${table(CoauthorModel)} co ON pr.proposal_id = co.proposal_id AND co.status = ${
CoauthorStatus.APPROVED
}
WHERE
p.type = ${ProposalType.Grant} AND
(lower(p.user) = lower(${userAddress}) OR
lower(co.address) = lower(${userAddress}) OR
lower(pe.address) = lower(${userAddress}))
GROUP BY
pr.id,
pr.proposal_id,
pr.status,
pr.title,
p.enacting_tx,
p.enacted_description,
p.vesting_addresses,
p.type,
p.configuration,
p.user,
p.created_at,
p.updated_at
ORDER BY p.created_at DESC;
`

const result = await this.namedQuery<UserProject>(`get_user_projects`, query)
return result || []
}
}
Loading

0 comments on commit 2b83ba6

Please sign in to comment.