From 67c1a4f88871b677946ddd154a4552513920947e Mon Sep 17 00:00:00 2001 From: Francisco Tobar Date: Fri, 15 Nov 2024 11:46:25 -0600 Subject: [PATCH] feat(cr_v2): metrics --- src/app/collective-rewards/hooks/index.ts | 1 + .../hooks/useGetGaugesFunction.ts | 65 +++++++++++++++++ .../metrics/AllTimeRewardsMetrics.tsx | 73 +++++++++++++++++++ .../metrics/CycleMetrics.tsx | 48 ++++++++++++ .../collective-rewards/metrics/Metrics.tsx | 52 +++++++++++++ .../metrics/MetricsSection.tsx | 17 ----- .../metrics/TotalActiveBuildersMetrics.tsx | 17 +++++ .../metrics/TotalAllocationsMetrics.tsx | 41 +++++++++++ .../metrics/components/CycleMetrics.tsx | 23 ------ .../WhitelistedBuildersLengthMetrics.tsx | 29 -------- .../metrics/components/index.ts | 2 - .../metrics/context/CycleContext.tsx | 15 ++-- .../collective-rewards/metrics/hooks/index.ts | 1 + .../metrics/hooks/useGetCycleNext.ts | 22 ++++++ src/app/collective-rewards/metrics/index.ts | 7 +- src/app/collective-rewards/page.tsx | 8 +- .../rewards/builders/EstimatedRewards.tsx | 7 +- .../rewards/utils/getLastCycleRewards.ts | 2 +- .../builder/BuilderRewardsSettingsMetrics.tsx | 2 +- .../utils/applyPrecision.ts | 3 + .../utils/getEpochCycle.test.tsx | 73 ------------------- src/app/collective-rewards/utils/index.ts | 1 + src/app/proposals/shared/supportedABIs.ts | 20 ++++- 23 files changed, 364 insertions(+), 165 deletions(-) create mode 100644 src/app/collective-rewards/hooks/index.ts create mode 100644 src/app/collective-rewards/hooks/useGetGaugesFunction.ts create mode 100644 src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx create mode 100644 src/app/collective-rewards/metrics/CycleMetrics.tsx create mode 100644 src/app/collective-rewards/metrics/Metrics.tsx delete mode 100644 src/app/collective-rewards/metrics/MetricsSection.tsx create mode 100644 src/app/collective-rewards/metrics/TotalActiveBuildersMetrics.tsx create mode 100644 src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx delete mode 100644 src/app/collective-rewards/metrics/components/CycleMetrics.tsx delete mode 100644 src/app/collective-rewards/metrics/components/WhitelistedBuildersLengthMetrics.tsx delete mode 100644 src/app/collective-rewards/metrics/components/index.ts create mode 100644 src/app/collective-rewards/metrics/hooks/useGetCycleNext.ts create mode 100644 src/app/collective-rewards/utils/applyPrecision.ts delete mode 100644 src/app/collective-rewards/utils/getEpochCycle.test.tsx diff --git a/src/app/collective-rewards/hooks/index.ts b/src/app/collective-rewards/hooks/index.ts new file mode 100644 index 00000000..db050a87 --- /dev/null +++ b/src/app/collective-rewards/hooks/index.ts @@ -0,0 +1 @@ +export * from './useGetGaugesFunction' diff --git a/src/app/collective-rewards/hooks/useGetGaugesFunction.ts b/src/app/collective-rewards/hooks/useGetGaugesFunction.ts new file mode 100644 index 00000000..b5af2d7e --- /dev/null +++ b/src/app/collective-rewards/hooks/useGetGaugesFunction.ts @@ -0,0 +1,65 @@ +import { GaugeAbi } from '@/lib/abis/v2/GaugeAbi' +import { Address } from 'viem' +import { useReadContracts } from 'wagmi' +import { AVERAGE_BLOCKTIME } from '@/lib/constants' +import { useMemo } from 'react' +import { AbiFunction } from 'viem' + +type FunctionEntry = Extract<(typeof GaugeAbi)[number], AbiFunction> +type FunctionName = Extract< + FunctionEntry['name'], + 'earned' | 'claimedBackerRewards' | 'estimatedBackerRewards' | 'totalAllocation' +> +type FunctionParams = FunctionEntry['inputs'] + +//TODO: Add the correct return type +export const useGaugesGetFunction = ( + gauges: Address[], + functionName: FunctionName, + functionParams?: FunctionParams, +) => { + const contractCalls = gauges.map(gauge => { + return { + address: gauge, + abi: GaugeAbi, + functionName, + args: functionParams, + } as const + }) + + const { + data: contractResults, + isLoading, + error, + } = useReadContracts({ + contracts: contractCalls, + query: { + refetchInterval: AVERAGE_BLOCKTIME, + }, + }) + + const gaugesMap = useMemo( + () => + gauges.reduce>((acc, gauge, index) => { + if (!contractResults) { + return {} as Record + } + + if (contractResults[index].error) { + throw contractResults[index].error + } + + const result = contractResults[index].result as FunctionResult + acc[gauge] = result + + return acc + }, {}), + [gauges, contractResults], + ) + + return { + data: gaugesMap, + isLoading, + error, + } +} diff --git a/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx b/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx new file mode 100644 index 00000000..0f540898 --- /dev/null +++ b/src/app/collective-rewards/metrics/AllTimeRewardsMetrics.tsx @@ -0,0 +1,73 @@ +import { Address } from 'viem' +import { + MetricsCard, + MetricsCardTitle, + useGetGaugesNotifyReward, + Token, + TokenMetricsCardRow, + formatMetrics, +} from '@/app/collective-rewards/rewards' +import { FC } from 'react' +import { useHandleErrors } from '@/app/collective-rewards/utils' +import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' +import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' +import { usePricesContext } from '@/shared/context/PricesContext' + +type TokenRewardsMetricsProps = { + gauges: Address[] + currency?: string + token: Token +} + +const TokenRewardsMetrics: FC = ({ + gauges, + token: { symbol, address }, + currency = 'USD', +}) => { + const { data, isLoading, error } = useGetGaugesNotifyReward(gauges, address) + + useHandleErrors({ error, title: 'Error loading all time rewards' }) + + const { prices } = usePricesContext() + + const totalRewards = Object.values(data).reduce( + (acc, events) => + acc + + events.reduce( + (acc, { args: { backersAmount_, builderAmount_ } }) => acc + backersAmount_ + builderAmount_, + 0n, + ), + 0n, + ) + + const totalRewardsInHuman = Number(formatBalanceToHuman(totalRewards)) + const price = prices[symbol]?.price ?? 0 + + const { amount, fiatAmount } = formatMetrics(totalRewardsInHuman, price, symbol, currency) + + return withSpinner( + TokenMetricsCardRow, + 'min-h-0 grow-0', + )({ + amount, + fiatAmount, + isLoading, + }) +} + +type AllTimeRewardsProps = { + gauges: Address[] + tokens: { + [token: string]: Token + } +} + +export const AllTimeRewardsMetrics: FC = ({ gauges, tokens: { rif, rbtc } }) => { + return ( + + + + + + ) +} diff --git a/src/app/collective-rewards/metrics/CycleMetrics.tsx b/src/app/collective-rewards/metrics/CycleMetrics.tsx new file mode 100644 index 00000000..67e0a2fe --- /dev/null +++ b/src/app/collective-rewards/metrics/CycleMetrics.tsx @@ -0,0 +1,48 @@ +import { MetricsCard } from '@/components/MetricsCard' +import { useCycleContext } from '@/app/collective-rewards/metrics' +import { Duration, DateTime } from 'luxon' +import { useEffect, useState } from 'react' + +let timeout: NodeJS.Timeout + +export const CycleMetrics = () => { + const [timeRemaining, setTimeRemaining] = useState(Duration.fromObject({ minutes: 0 })) + let { + data: { cycleDuration, cycleNext }, + } = useCycleContext() + + const duration = + cycleDuration.as('days') < 1 ? cycleDuration.shiftTo('hours') : cycleDuration.shiftTo('days') + + useEffect(() => { + if (timeRemaining.as('minutes') > 0) { + timeout = setTimeout(() => { + setTimeRemaining(state => state.minus({ minutes: 1 })) + }, 60000) // every minute + } else { + clearTimeout(timeout) + } + + return () => clearTimeout(timeout) + }, [timeRemaining.minutes]) + + useEffect(() => { + const now = DateTime.now() + let diff = cycleNext.diff(now, ['days']).mapUnits(unit => Math.floor(unit)) + + if (diff.as('days') < 1) { + diff = cycleNext.diff(now, ['hours', 'minutes']).mapUnits(unit => Math.floor(unit)) + } + + setTimeRemaining(diff) + }, [cycleNext]) + + return ( + + ) +} diff --git a/src/app/collective-rewards/metrics/Metrics.tsx b/src/app/collective-rewards/metrics/Metrics.tsx new file mode 100644 index 00000000..55ab7b23 --- /dev/null +++ b/src/app/collective-rewards/metrics/Metrics.tsx @@ -0,0 +1,52 @@ +import { useGetGaugesArray, withBuilderButton } from '@/app/collective-rewards/user' +import { HeaderTitle } from '@/components/Typography' +import { + TotalAllocationsMetrics, + CycleMetrics, + CycleContextProvider, + TotalActiveBuildersMetrics, + AllTimeRewardsMetrics, +} from '@/app/collective-rewards/metrics' +import { getAddress } from 'viem' +import { tokenContracts } from '@/lib/contracts' +import { getCoinbaseAddress } from '@/app/collective-rewards/utils' +import { PricesContextProvider } from '@/shared/context/PricesContext' + +const HeaderWithBuilderButton = withBuilderButton(HeaderTitle) + +export const Metrics = () => { + const { data: activeGauges } = useGetGaugesArray('active') + const gauges = activeGauges ?? [] + + const tokens = { + rif: { + address: getAddress(tokenContracts.RIF), + symbol: 'RIF', + }, + rbtc: { + address: getCoinbaseAddress(), + symbol: 'RBTC', + }, + } + + return ( +
+ Metrics + + +
+
+ + + + +
+
+ +
+
+
+
+
+ ) +} diff --git a/src/app/collective-rewards/metrics/MetricsSection.tsx b/src/app/collective-rewards/metrics/MetricsSection.tsx deleted file mode 100644 index 4274eba8..00000000 --- a/src/app/collective-rewards/metrics/MetricsSection.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { withBuilderButton } from '@/app/collective-rewards/user' -import { HeaderTitle } from '@/components/Typography' -import { CycleMetrics, WhitelistedBuildersLengthMetrics } from './components' - -const HeaderWithBuilderButton = withBuilderButton(HeaderTitle) - -export const MetricsSection = () => { - return ( -
- Metrics -
- - -
-
- ) -} diff --git a/src/app/collective-rewards/metrics/TotalActiveBuildersMetrics.tsx b/src/app/collective-rewards/metrics/TotalActiveBuildersMetrics.tsx new file mode 100644 index 00000000..60b0370b --- /dev/null +++ b/src/app/collective-rewards/metrics/TotalActiveBuildersMetrics.tsx @@ -0,0 +1,17 @@ +import { MetricsCardWithSpinner } from '@/components/MetricsCard/MetricsCard' +import { useGetGaugesLength } from '../user' +import { useHandleErrors } from '@/app/collective-rewards/utils' + +export const TotalActiveBuildersMetrics = () => { + const { data, isLoading, error } = useGetGaugesLength('active') + useHandleErrors({ error, title: 'Error loading active builders' }) + + return ( + + ) +} diff --git a/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx b/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx new file mode 100644 index 00000000..546ef66f --- /dev/null +++ b/src/app/collective-rewards/metrics/TotalAllocationsMetrics.tsx @@ -0,0 +1,41 @@ +import { useGaugesGetFunction } from '@/app/collective-rewards/hooks' +import { Address } from 'viem' +import { useHandleErrors } from '@/app/collective-rewards/utils' +import { MetricsCardWithSpinner } from '@/components/MetricsCard/MetricsCard' +import { FC } from 'react' +import { usePricesContext } from '@/shared/context/PricesContext' +import { Token } from '@/app/collective-rewards/rewards' +import { formatCurrency } from '@/lib/utils' +import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' + +type TotalAllocationsProps = { + gauges: Address[] + currency?: string + token: Token +} + +export const TotalAllocationsMetrics: FC = ({ + gauges, + token: { symbol }, + currency = 'USD', +}) => { + const { prices } = usePricesContext() + const { data, isLoading, error } = useGaugesGetFunction(gauges, 'totalAllocation', []) + useHandleErrors({ error, title: 'Error loading total allocations' }) + + const price = prices[symbol]?.price ?? 0 + + const totalAllocations = Object.values(data).reduce((acc, allocation) => acc + allocation, 0n) + const totalAllocationsInHuman = Number(formatBalanceToHuman(totalAllocations)) + const fiatAmount = `= ${currency} ${formatCurrency(totalAllocationsInHuman * price, currency)}` + + return ( + + ) +} diff --git a/src/app/collective-rewards/metrics/components/CycleMetrics.tsx b/src/app/collective-rewards/metrics/components/CycleMetrics.tsx deleted file mode 100644 index 7d59b6e6..00000000 --- a/src/app/collective-rewards/metrics/components/CycleMetrics.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { MetricsCard } from '@/components/MetricsCard' -import { getCycle } from '@/app/collective-rewards/utils/getCycle' - -export const CycleMetrics = () => { - const { remainingDays, cycleDuration, cycleEndTimestamp } = getCycle() - const remainingDaysString: string = - remainingDays.as('day') > 1 - ? `${Math.floor(remainingDays.as('day'))} days` - : remainingDays.as('hour') > 1 - ? `${Math.floor(remainingDays.as('hours'))} hours` - : remainingDays.as('minute') > 1 - ? `${Math.floor(remainingDays.as('minutes'))} minutes` - : '1 minute' - - return ( - - ) -} diff --git a/src/app/collective-rewards/metrics/components/WhitelistedBuildersLengthMetrics.tsx b/src/app/collective-rewards/metrics/components/WhitelistedBuildersLengthMetrics.tsx deleted file mode 100644 index 05c864ff..00000000 --- a/src/app/collective-rewards/metrics/components/WhitelistedBuildersLengthMetrics.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { MetricsCard } from '@/components/MetricsCard' -import { useGetWhitelistedBuildersLength } from '@/app/collective-rewards/user/hooks/useGetWhitelistedBuildersLength' -import { useAlertContext } from '@/app/providers' -import { useEffect } from 'react' - -export const WhitelistedBuildersLengthMetrics = () => { - const { data: wlBuildersLength, error: whitelistedBuildersError } = useGetWhitelistedBuildersLength() - const { setMessage: setErrorMessage } = useAlertContext() - - useEffect(() => { - if (whitelistedBuildersError) { - setErrorMessage({ - severity: 'error', - title: 'Error loading whitelisted builders', - content: whitelistedBuildersError.message, - }) - console.error('🐛 whitelistedBuildersError:', whitelistedBuildersError) - } - }, [whitelistedBuildersError, setErrorMessage]) - - /* TODO: MetricsCard should support loading and error status */ - return ( - - ) -} diff --git a/src/app/collective-rewards/metrics/components/index.ts b/src/app/collective-rewards/metrics/components/index.ts deleted file mode 100644 index 813a12ef..00000000 --- a/src/app/collective-rewards/metrics/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './CycleMetrics' -export * from './WhitelistedBuildersLengthMetrics' diff --git a/src/app/collective-rewards/metrics/context/CycleContext.tsx b/src/app/collective-rewards/metrics/context/CycleContext.tsx index 9a9952f3..cb5407ea 100644 --- a/src/app/collective-rewards/metrics/context/CycleContext.tsx +++ b/src/app/collective-rewards/metrics/context/CycleContext.tsx @@ -1,6 +1,7 @@ import { createContext, FC, ReactNode, useContext } from 'react' -import { DateTime } from 'luxon' +import { DateTime, Duration } from 'luxon' import { + useGetCycleNext, useGetCycleStart, useGetCycleStartAndDuration, useGetEndDistributionWindow, @@ -8,7 +9,8 @@ import { export type Cycle = { cycleStart: DateTime - cycleDuration: DateTime + cycleDuration: Duration + cycleNext: DateTime fistCycleStart: DateTime endDistributionWindow: DateTime } @@ -47,14 +49,17 @@ export const CycleContextProvider: FC = ({ children }) => { isLoading: endDistributionWindowLoading, error: endDistributionWindowError, } = useGetEndDistributionWindow(timestamp) + const { data: cycleNext, isLoading: cycleNextLoading, error: cycleNextError } = useGetCycleNext(timestamp) - const isLoading = cycleStartAndDurationLoading || cycleStartLoading || endDistributionWindowLoading - const error = cycleStartAndDurationError ?? cycleStartError ?? endDistributionWindowError + const isLoading = + cycleStartAndDurationLoading || cycleStartLoading || endDistributionWindowLoading || cycleNextLoading + const error = cycleStartAndDurationError ?? cycleStartError ?? endDistributionWindowError ?? cycleNextError const valueOfContext: CycleContextValue = { data: { cycleStart: DateTime.fromSeconds(Number(cycleStart ?? 0n)), - cycleDuration: DateTime.fromSeconds(Number(cycleDuration ?? 0n)), + cycleNext: DateTime.fromSeconds(Number(cycleNext ?? 0n)), + cycleDuration: Duration.fromObject({ seconds: Number(cycleDuration ?? 0n) }), fistCycleStart: DateTime.fromSeconds(Number(fistCycleStart ?? 0n)), endDistributionWindow: DateTime.fromSeconds(Number(endDistributionWindow ?? 0n)), }, diff --git a/src/app/collective-rewards/metrics/hooks/index.ts b/src/app/collective-rewards/metrics/hooks/index.ts index dd83692f..d6460464 100644 --- a/src/app/collective-rewards/metrics/hooks/index.ts +++ b/src/app/collective-rewards/metrics/hooks/index.ts @@ -1,3 +1,4 @@ export * from './useGetCycleStart' export * from './useGetCycleStartAndDuration' export * from './useGetEndDistributionWindow' +export * from './useGetCycleNext' diff --git a/src/app/collective-rewards/metrics/hooks/useGetCycleNext.ts b/src/app/collective-rewards/metrics/hooks/useGetCycleNext.ts new file mode 100644 index 00000000..127bf906 --- /dev/null +++ b/src/app/collective-rewards/metrics/hooks/useGetCycleNext.ts @@ -0,0 +1,22 @@ +import { CycleTimeKeeperAbi } from '@/lib/abis/v2/CycleTimeKeeperAbi' +import { BackersManagerAddress } from '@/lib/contracts' +import { useReadContract } from 'wagmi' +import { AVERAGE_BLOCKTIME } from '@/lib/constants' + +export const useGetCycleNext = (timestamp: bigint) => { + const { data, isLoading, error } = useReadContract({ + address: BackersManagerAddress, + abi: CycleTimeKeeperAbi, + functionName: 'cycleNext', + args: [timestamp], + query: { + refetchInterval: AVERAGE_BLOCKTIME, + }, + }) + + return { + data, + isLoading, + error, + } +} diff --git a/src/app/collective-rewards/metrics/index.ts b/src/app/collective-rewards/metrics/index.ts index 453d09c7..6441ae31 100644 --- a/src/app/collective-rewards/metrics/index.ts +++ b/src/app/collective-rewards/metrics/index.ts @@ -1,4 +1,7 @@ -export * from './components' export * from './context' export * from './hooks' -export * from './MetricsSection' +export * from './Metrics' +export * from './TotalAllocationsMetrics' +export * from './CycleMetrics' +export * from './TotalActiveBuildersMetrics' +export * from './AllTimeRewardsMetrics' diff --git a/src/app/collective-rewards/page.tsx b/src/app/collective-rewards/page.tsx index d7c18941..5f51496a 100644 --- a/src/app/collective-rewards/page.tsx +++ b/src/app/collective-rewards/page.tsx @@ -1,7 +1,7 @@ 'use client' import { LeaderBoard } from '@/app/collective-rewards/leaderboard' -import { MetricsSection } from '@/app/collective-rewards/metrics' +import { Metrics } from '@/app/collective-rewards/metrics' import { WhitelistContextProviderWithBuilders, WhitelistSection } from '@/app/collective-rewards/whitelist' import { MainContainer } from '@/components/MainContainer/MainContainer' @@ -9,11 +9,7 @@ export default function BuildersIncentiveMarket() { return (
- - - - - +
) diff --git a/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx b/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx index f494be4e..1de5d886 100644 --- a/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx +++ b/src/app/collective-rewards/rewards/builders/EstimatedRewards.tsx @@ -10,11 +10,11 @@ import { RewardDetails, Token, } from '@/app/collective-rewards/rewards' -import { useHandleErrors } from '@/app/collective-rewards/utils' +import { useHandleErrors, applyPrecision } from '@/app/collective-rewards/utils' import { formatBalanceToHuman } from '@/app/user/Balances/balanceUtils' import { usePricesContext } from '@/shared/context/PricesContext' import { FC, useEffect, useState } from 'react' -import { Address, parseUnits } from 'viem' +import { Address } from 'viem' import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' type TokenRewardsProps = { @@ -63,8 +63,7 @@ const TokenRewards: FC = ({ builder, gauge, token: { id, symb const { prices } = usePricesContext() - const precision = parseUnits('1', 18) - const rewardsAmount = rewardPercentage ? (rewards * rewardPercentage) / precision : 0n + const rewardsAmount = rewardPercentage ? applyPrecision(rewards * rewardPercentage) : 0n const estimatedRewards = rewardShares && totalPotentialRewards ? (rewardShares * rewardsAmount) / totalPotentialRewards : 0n diff --git a/src/app/collective-rewards/rewards/utils/getLastCycleRewards.ts b/src/app/collective-rewards/rewards/utils/getLastCycleRewards.ts index db68ea10..668c8838 100644 --- a/src/app/collective-rewards/rewards/utils/getLastCycleRewards.ts +++ b/src/app/collective-rewards/rewards/utils/getLastCycleRewards.ts @@ -10,7 +10,7 @@ type CycleRewards = { export const getLastCycleRewards = (cycle: Cycle, notifyRewardLogs?: GaugeNotifyRewardEventLog) => { const { cycleDuration, endDistributionWindow, cycleStart } = cycle const distributionWindow = endDistributionWindow.diff(cycleStart) - const lastCycleStart = cycleStart.minus({ millisecond: +cycleDuration }) + const lastCycleStart = cycleStart.minus({ millisecond: +cycleDuration.as('millisecond') }) const lastCycleAfterDistribution = lastCycleStart.plus({ millisecond: +distributionWindow }) if (!notifyRewardLogs) { diff --git a/src/app/collective-rewards/settings/builder/BuilderRewardsSettingsMetrics.tsx b/src/app/collective-rewards/settings/builder/BuilderRewardsSettingsMetrics.tsx index 347f9eb9..e21ff924 100644 --- a/src/app/collective-rewards/settings/builder/BuilderRewardsSettingsMetrics.tsx +++ b/src/app/collective-rewards/settings/builder/BuilderRewardsSettingsMetrics.tsx @@ -3,7 +3,7 @@ import { withSpinner } from '@/components/LoadingSpinner/withLoadingSpinner' import { Typography } from '@/components/Typography' import { cn } from '@/lib/utils' import { DateTime } from 'luxon' -import { FC, HTMLAttributes, ReactNode, useEffect } from 'react' +import { FC, HTMLAttributes, ReactNode } from 'react' import { weiToPercentage } from '../utils' import { useBuilderSettingsContext } from './context' diff --git a/src/app/collective-rewards/utils/applyPrecision.ts b/src/app/collective-rewards/utils/applyPrecision.ts new file mode 100644 index 00000000..f6abe96f --- /dev/null +++ b/src/app/collective-rewards/utils/applyPrecision.ts @@ -0,0 +1,3 @@ +import { parseUnits } from 'viem' + +export const applyPrecision = (value: bigint, precision = 18) => value / parseUnits('1', precision) diff --git a/src/app/collective-rewards/utils/getEpochCycle.test.tsx b/src/app/collective-rewards/utils/getEpochCycle.test.tsx deleted file mode 100644 index 5d4882eb..00000000 --- a/src/app/collective-rewards/utils/getEpochCycle.test.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { describe, test, expect } from 'vitest' -import { - getCycleDurationInDays, - getCycleEndTimestamp, - daysToMillis, -} from '@/app/collective-rewards/utils/getCycle' -import { DateTime, Duration } from 'luxon' - -describe('getCycleDurationInDays', () => { - test.each([0, 1, 7, 30, 45])('should return the correct cycle duration for %i days', expectedDays => { - const durationInMillis = daysToMillis(expectedDays) - expect(getCycleDurationInDays(durationInMillis)).toBe(expectedDays.toString()) - }) -}) - -describe('getCycleEndTimestamp', () => { - const now = DateTime.now() - /* - * All math is done in milliseconds, to avoid calendar math. - * See calendar vs time math https://moment.github.io/luxon/#/math?id=calendar-math-vs-time-math - */ - test.each([ - { - startDate: now.toMillis(), - cycleDurationInMillis: daysToMillis(7), - expectedCycleEndTimestamp: now.plus({ milliseconds: daysToMillis(7) }), - now, - }, - { - startDate: now.minus(Duration.fromObject({ milliseconds: daysToMillis(6) })).toMillis(), - cycleDurationInMillis: daysToMillis(7), - expectedCycleEndTimestamp: now.plus({ milliseconds: daysToMillis(1) }), - now, - }, - { - startDate: now.minus(Duration.fromObject({ milliseconds: daysToMillis(8) })).toMillis(), - cycleDurationInMillis: daysToMillis(7), - expectedCycleEndTimestamp: now.plus({ milliseconds: daysToMillis(6) }), - now, - }, - { - startDate: now - .minus( - Duration.fromMillis( - Duration.fromObject({ - days: 6, - hours: 23, - minutes: 59, - seconds: 59, - milliseconds: 999, - }).toMillis(), - ), - ) - .toMillis(), - cycleDurationInMillis: daysToMillis(7), - expectedCycleEndTimestamp: now.plus(Duration.fromObject({ milliseconds: 1 })), - now, - }, - { - startDate: now.minus(Duration.fromObject({ milliseconds: daysToMillis(23) })).toMillis(), - cycleDurationInMillis: daysToMillis(7), - expectedCycleEndTimestamp: now.plus(Duration.fromObject({ milliseconds: daysToMillis(5) })), - now, - }, - ])( - 'should return the correct cycle end timestamp with (start: $startDate, duration: $cycleDurationInMillis)', - ({ startDate, cycleDurationInMillis, expectedCycleEndTimestamp, now }) => { - expect(getCycleEndTimestamp(startDate, cycleDurationInMillis, now).toISO()).toBe( - expectedCycleEndTimestamp.toISO(), - ) - }, - ) -}) diff --git a/src/app/collective-rewards/utils/index.ts b/src/app/collective-rewards/utils/index.ts index 5abf3f6e..e4351a23 100644 --- a/src/app/collective-rewards/utils/index.ts +++ b/src/app/collective-rewards/utils/index.ts @@ -1,2 +1,3 @@ export * from './handleErrors' export * from './getCoinbaseAddress' +export * from './applyPrecision' diff --git a/src/app/proposals/shared/supportedABIs.ts b/src/app/proposals/shared/supportedABIs.ts index 034c075c..864dd6db 100644 --- a/src/app/proposals/shared/supportedABIs.ts +++ b/src/app/proposals/shared/supportedABIs.ts @@ -3,7 +3,7 @@ import { RIFTokenAbi } from '@/lib/abis/RIFTokenAbi' import { SimplifiedRewardDistributorAbi } from '@/lib/abis/SimplifiedRewardDistributorAbi' import { BuilderRegistryAbi } from '@/lib/abis/v2/BuilderRegistryAbi' import { HTMLProps } from 'react' -import { AbiFunction, AbiParameterToPrimitiveType } from 'viem' +import { Abi, AbiFunction, AbiParameterToPrimitiveType } from 'viem' export const abiNames = [ 'DAOTreasuryAbi', @@ -31,7 +31,7 @@ export const supportedProposalActions = [ export type SupportedActionAbiName = (typeof abiNames)[number] -export type SupportedActionAbi = (typeof abis)[number] +export type SupportedActionAbi = A[number] export type AbiEntry = SupportedActionAbi[number] @@ -45,6 +45,12 @@ export type InputParameter = FunctionInputs[number] export type InputParameterName = InputParameter['name'] +export type FunctionOutputs = FunctionEntry['outputs'] + +export type OutputParameter = FunctionOutputs[number] + +export type OutputParameterName = OutputParameter['name'] + export type FunctionEntryByName = Extract export type SupportedProposalAction = FunctionEntryByName<(typeof supportedProposalActions)[number]> @@ -56,11 +62,21 @@ export type InputParameterByFnByName +export type OutputParameterByFnByName = Extract< + FunctionEntryByName['outputs'][number], + { name: O } +> + export type InputParameterTypeByFnByName< F = SupportedProposalActionName, I = InputParameterName, > = AbiParameterToPrimitiveType> +export type OutputParameterTypeByFnByName< + F = SupportedProposalActionName, + O = OutputParameterName, +> = AbiParameterToPrimitiveType> + export type InputNameFormatMap = { [I in InputParameterByFnByName as I['name']]: string }