From 838c9b4cbcba0fc235d6596a02a1c479bba3b998 Mon Sep 17 00:00:00 2001 From: 1emu Date: Tue, 6 Aug 2024 14:56:26 -0300 Subject: [PATCH] refactor: address pr comments --- src/clients/VestingData.ts | 114 ++----------------------- src/services/VestingService.ts | 151 +++++++++++++++++++++++++++++---- 2 files changed, 139 insertions(+), 126 deletions(-) diff --git a/src/clients/VestingData.ts b/src/clients/VestingData.ts index 16c52072e..d68a2b06c 100644 --- a/src/clients/VestingData.ts +++ b/src/clients/VestingData.ts @@ -11,8 +11,6 @@ import VESTING_V2_ABI from '../utils/contracts/abi/vesting/vesting_v2.json' import { ContractVersion, TopicsByVersion } from '../utils/contracts/vesting' import { ErrorCategory } from '../utils/errorCategories' -import { SubgraphVesting } from './VestingSubgraphTypes' - export type VestingLog = { topic: string timestamp: string @@ -35,11 +33,11 @@ export type Vesting = { export type VestingWithLogs = Vesting & { logs: VestingLog[] } -function toISOString(seconds: number) { +export function toISOString(seconds: number) { return new Date(seconds * 1000).toISOString() } -function getVestingDates(contractStart: number, contractEndsTimestamp: number) { +export function getVestingDates(contractStart: number, contractEndsTimestamp: number) { const vestingStartAt = toISOString(contractStart) const vestingFinishAt = toISOString(contractEndsTimestamp) return { @@ -88,7 +86,7 @@ async function getVestingContractLogs(vestingAddress: string, provider: JsonRpcP return logsData } -function getInitialVestingStatus(startAt: string, finishAt: string) { +export function getInitialVestingStatus(startAt: string, finishAt: string) { const now = new Date() if (now < new Date(startAt)) { return VestingStatus.Pending @@ -196,107 +194,7 @@ async function getVestingContractDataV2( } } -export function parseVestingData(vestingData: SubgraphVesting): Vesting { - const contractStart = Number(vestingData.start) - const contractDuration = Number(vestingData.duration) - const cliffEnd = Number(vestingData.cliff) - const currentTime = Math.floor(Date.now() / 1000) - - const start_at = toISOString(contractStart) - const contractEndsTimestamp = contractStart + contractDuration - const finish_at = toISOString(contractEndsTimestamp) - - const released = Number(vestingData.released) - const total = Number(vestingData.total) - let vested = 0 - - if (currentTime < cliffEnd) { - // If we're before the cliff end, nothing is vested - vested = 0 - } else if (vestingData.linear) { - // Linear vesting after the cliff - if (currentTime >= contractEndsTimestamp) { - vested = total - } else { - const timeElapsed = currentTime - contractStart - vested = (timeElapsed / contractDuration) * total - } - } else { - // Periodic vesting after the cliff - const periodDuration = Number(vestingData.periodDuration) - let timeVested = currentTime - contractStart - - // Adjust for pauses (we only use the latest pause log. If unpaused, it resumes as if it'd have never been paused) - if (vestingData.paused) { - if (vestingData.pausedLogs && vestingData.pausedLogs.length > 0) { - const latestPauseLog = vestingData.pausedLogs.reduce((latestLog, currentLog) => { - return Number(currentLog.timestamp) > Number(latestLog.timestamp) ? currentLog : latestLog - }, vestingData.pausedLogs[0]) - const pauseTimestamp = Number(latestPauseLog.timestamp) - if (currentTime >= pauseTimestamp) { - timeVested = pauseTimestamp - contractStart - } - } - } - - const periodsCompleted = Math.floor(timeVested / periodDuration) - - // Sum vested tokens for completed periods - for (let i = 0; i < periodsCompleted && i < vestingData.vestedPerPeriod.length; i++) { - vested += Number(vestingData.vestedPerPeriod[i]) - } - } - - const releasable = vested - released - - let status = getInitialVestingStatus(start_at, finish_at) - if (vestingData.revoked) { - status = VestingStatus.Revoked - } else if (vestingData.paused) { - status = VestingStatus.Paused - } - - const token = getTokenSymbolFromAddress(vestingData.token) - - return { - address: vestingData.id, - cliff: toISOString(cliffEnd), - vestedPerPeriod: vestingData.vestedPerPeriod.map(Number), - ...getVestingDates(contractStart, contractEndsTimestamp), - vested, - released, - releasable, - total, - token, - status, - start_at, - finish_at, - } -} - -export function parseVestingLogs(vestingData: SubgraphVesting) { - const version = vestingData.linear ? ContractVersion.V1 : ContractVersion.V2 - const topics = TopicsByVersion[version] - const logs: VestingLog[] = [] - const parsedReleases: VestingLog[] = vestingData.releaseLogs.map((releaseLog) => { - return { - topic: topics.RELEASE, - timestamp: toISOString(Number(releaseLog.timestamp)), - amount: Number(releaseLog.amount), - } - }) - logs.push(...parsedReleases) - const parsedPauseEvents: VestingLog[] = vestingData.pausedLogs.map((pausedLog) => { - return { - topic: pausedLog.eventType === 'Paused' ? topics.PAUSED : topics.UNPAUSED, - timestamp: toISOString(Number(pausedLog.timestamp)), - } - }) - logs.push(...parsedPauseEvents) - return logs.sort(sortByTimestamp) -} - -function sortByTimestamp(a: VestingLog, b: VestingLog) { +export function sortByTimestamp(a: VestingLog, b: VestingLog) { return new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() } @@ -323,7 +221,7 @@ export async function getVestingWithLogsFromAlchemy(vestingAddress: string, prop address: vestingAddress, } } catch (errorV1) { - ErrorService.report('Unable to fetch vesting contract data', { + ErrorService.report('Unable to fetch vesting contract data from alchemy', { proposalId, errorV2: `${errorV2}`, errorV1: `${errorV1}`, @@ -334,7 +232,7 @@ export async function getVestingWithLogsFromAlchemy(vestingAddress: string, prop } } -function getTokenSymbolFromAddress(tokenAddress: string) { +export function getTokenSymbolFromAddress(tokenAddress: string) { switch (tokenAddress) { case '0x0f5d2fb29fb7d3cfee444a200298f468908cc942': return 'MANA' diff --git a/src/services/VestingService.ts b/src/services/VestingService.ts index 2b7d63fa3..d936872a4 100644 --- a/src/services/VestingService.ts +++ b/src/services/VestingService.ts @@ -1,7 +1,19 @@ import { Transparency, TransparencyVesting } from '../clients/Transparency' -import { VestingWithLogs, parseVestingData, parseVestingLogs } from '../clients/VestingData' +import { + Vesting, + VestingLog, + VestingWithLogs, + getInitialVestingStatus, + getTokenSymbolFromAddress, + getVestingDates, + getVestingWithLogsFromAlchemy, + sortByTimestamp, + toISOString, +} from '../clients/VestingData' import { SubgraphVesting } from '../clients/VestingSubgraphTypes' import { VestingsSubgraph } from '../clients/VestingsSubgraph' +import { VestingStatus } from '../entities/Grant/types' +import { ContractVersion, TopicsByVersion } from '../utils/contracts/vesting' import { ErrorCategory } from '../utils/errorCategories' import CacheService, { TTL_24_HS } from './CacheService' @@ -23,7 +35,7 @@ export class VestingService { static async getVestings(addresses: string[]): Promise { const vestingsData = await VestingsSubgraph.get().getVestings(addresses) - return vestingsData.map(this.parseSubgraphVesting).sort(compareVestingInfo) + return vestingsData.map(this.parseSubgraphVesting).sort(this.sortVestingsByDate) } static async getVestingWithLogs( @@ -34,7 +46,11 @@ export class VestingService { throw new Error('Unable to fetch vesting data for empty contract address') } - return await this.getVestingWithLogsFromSubgraph(vestingAddress, proposalId) + try { + return await this.getVestingWithLogsFromSubgraph(vestingAddress, proposalId) + } catch (error) { + return await getVestingWithLogsFromAlchemy(vestingAddress, proposalId) + } } private static async getVestingWithLogsFromSubgraph( @@ -45,7 +61,6 @@ export class VestingService { const subgraphVesting = await VestingsSubgraph.get().getVesting(vestingAddress) return this.parseSubgraphVesting(subgraphVesting) } catch (error) { - console.log('Unable to fetch vestings subgraph data', error) //TODO: remove before merging to master ErrorService.report('Unable to fetch vestings subgraph data', { error, vestingAddress, @@ -57,27 +72,127 @@ export class VestingService { } private static parseSubgraphVesting(vestingData: SubgraphVesting) { - const vestingContract = parseVestingData(vestingData) - const logs = parseVestingLogs(vestingData) + const vestingContract = this.parseVestingData(vestingData) + const logs = this.parseVestingLogs(vestingData) return { ...vestingContract, logs } } -} -function compareVestingInfo(a: VestingWithLogs, b: VestingWithLogs): number { - if (a.logs.length === 0 && b.logs.length === 0) { - return new Date(b.start_at).getTime() - new Date(a.start_at).getTime() - } + private static parseVestingData(vestingData: SubgraphVesting): Vesting { + const contractStart = Number(vestingData.start) + const contractDuration = Number(vestingData.duration) + const cliffEnd = Number(vestingData.cliff) + const currentTime = Math.floor(Date.now() / 1000) + + const start_at = toISOString(contractStart) + const contractEndsTimestamp = contractStart + contractDuration + const finish_at = toISOString(contractEndsTimestamp) + + const released = Number(vestingData.released) + const total = Number(vestingData.total) + let vested = 0 + + if (currentTime < cliffEnd) { + // If we're before the cliff end, nothing is vested + vested = 0 + } else if (vestingData.linear) { + // Linear vesting after the cliff + if (currentTime >= contractEndsTimestamp) { + vested = total + } else { + const timeElapsed = currentTime - contractStart + vested = (timeElapsed / contractDuration) * total + } + } else { + // Periodic vesting after the cliff + const periodDuration = Number(vestingData.periodDuration) + let timeVested = currentTime - contractStart + + // Adjust for pauses (we only use the latest pause log. If unpaused, it resumes as if it'd have never been paused) + if (vestingData.paused) { + if (vestingData.pausedLogs && vestingData.pausedLogs.length > 0) { + const latestPauseLog = vestingData.pausedLogs.reduce((latestLog, currentLog) => { + return Number(currentLog.timestamp) > Number(latestLog.timestamp) ? currentLog : latestLog + }, vestingData.pausedLogs[0]) + const pauseTimestamp = Number(latestPauseLog.timestamp) + if (currentTime >= pauseTimestamp) { + timeVested = pauseTimestamp - contractStart + } + } + } + + const periodsCompleted = Math.floor(timeVested / periodDuration) + + // Sum vested tokens for completed periods + for (let i = 0; i < periodsCompleted && i < vestingData.vestedPerPeriod.length; i++) { + vested += Number(vestingData.vestedPerPeriod[i]) + } + } - if (a.logs.length === 0) { - return -1 + const releasable = vested - released + + let status = getInitialVestingStatus(start_at, finish_at) + if (vestingData.revoked) { + status = VestingStatus.Revoked + } else if (vestingData.paused) { + status = VestingStatus.Paused + } + + const token = getTokenSymbolFromAddress(vestingData.token) + + return { + address: vestingData.id, + cliff: toISOString(cliffEnd), + vestedPerPeriod: vestingData.vestedPerPeriod.map(Number), + ...getVestingDates(contractStart, contractEndsTimestamp), + vested, + released, + releasable, + total, + token, + status, + start_at, + finish_at, + } } - if (b.logs.length === 0) { - return 1 + private static parseVestingLogs(vestingData: SubgraphVesting) { + const version = vestingData.linear ? ContractVersion.V1 : ContractVersion.V2 + const topics = TopicsByVersion[version] + const logs: VestingLog[] = [] + const parsedReleases: VestingLog[] = vestingData.releaseLogs.map((releaseLog) => { + return { + topic: topics.RELEASE, + timestamp: toISOString(Number(releaseLog.timestamp)), + amount: Number(releaseLog.amount), + } + }) + logs.push(...parsedReleases) + const parsedPauseEvents: VestingLog[] = vestingData.pausedLogs.map((pausedLog) => { + return { + topic: pausedLog.eventType === 'Paused' ? topics.PAUSED : topics.UNPAUSED, + timestamp: toISOString(Number(pausedLog.timestamp)), + } + }) + logs.push(...parsedPauseEvents) + return logs.sort(sortByTimestamp) } - const aLatestLogTimestamp = new Date(a.logs[0].timestamp).getTime() - const bLatestLogTimestamp = new Date(b.logs[0].timestamp).getTime() + private static sortVestingsByDate(a: VestingWithLogs, b: VestingWithLogs): number { + if (a.logs.length === 0 && b.logs.length === 0) { + return new Date(b.start_at).getTime() - new Date(a.start_at).getTime() + } + + if (a.logs.length === 0) { + return -1 + } + + if (b.logs.length === 0) { + return 1 + } + + const aLatestLogTimestamp = new Date(a.logs[0].timestamp).getTime() + const bLatestLogTimestamp = new Date(b.logs[0].timestamp).getTime() - return bLatestLogTimestamp - aLatestLogTimestamp + return bLatestLogTimestamp - aLatestLogTimestamp + } }