Skip to content

Commit

Permalink
refactor: add unit tests for UptimeModule
Browse files Browse the repository at this point in the history
  • Loading branch information
dawidsowardx committed Oct 11, 2024
1 parent 4393807 commit 911c0b4
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 20 deletions.
112 changes: 112 additions & 0 deletions apps/dashboard/src/lib/validators/uptime-module.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type { ValidatorCollectionItem } from '@common/gateway-sdk'
import { UptimeModule } from './uptime-module'
import { describe, it, expect, vi, type Mock, beforeEach } from 'vitest'
import BigNumber from 'bignumber.js'
import { ok } from 'neverthrow'

describe('uptime module', () => {
let uptimeModule: UptimeModule
let getUptimeFromDateSpy = vi.fn()
let getValidatorsUptimeSpy = vi.fn().mockReturnValue(getUptimeFromDateSpy)

beforeEach(() => {
uptimeModule = UptimeModule({
getValidatorUptimeSinceDate: getValidatorsUptimeSpy
})
})

describe('initial state, without validators set', () => {
it('should do nothing when called without uptime', () => {
const result = uptimeModule.maybeQueryUptime(undefined)
expect(getValidatorsUptimeSpy).not.toHaveBeenCalled()
expect(result).toBeUndefined()
})

it('should throw when trying to query uptime', () => {
expect(() => uptimeModule.maybeQueryUptime('1month')).toThrowError(
/Validators not set/
)
})

it('should return empty object when getting data', () => {
expect(uptimeModule.getDataForUptime('1month')).toEqual({})
})

it('should throw when trying to get APY', () => {
expect(() =>
uptimeModule.getApy({} as ValidatorCollectionItem, '1month')
).toThrowError(/Invalid totalAmountStaked/)
})
})

describe('with validators', () => {
beforeEach(() => {
uptimeModule.setValidators(
Promise.resolve([
{ address: '1', totalStakeInXRD: new BigNumber(1) },
{ address: '2', totalStakeInXRD: new BigNumber(2) }
])
)
})

it('should not throw when getting APY', () => {
expect(() =>
uptimeModule.getApy(
{
address: '1',
effective_fee_factor: { current: { fee_factor: '0' } }
} as unknown as ValidatorCollectionItem,
'1month'
)
).not.toThrow()
})

describe('with `maybeQueryUptime` called', () => {
let data: any
beforeEach(async () => {
getUptimeFromDateSpy.mockResolvedValueOnce(
ok({
'1': 0.5,
'2': 0.75
})
)

data = await uptimeModule.maybeQueryUptime('1month')
})

it('should call getValidatorUptimeSinceDate', () => {
expect(getUptimeFromDateSpy).toHaveBeenCalled()
expect(data).toEqual({
'1': 0.5,
'2': 0.75
})
})

it('should calculate APY correctly', async () => {
const apy = uptimeModule.getApy(
{
address: '1',
effective_fee_factor: { current: { fee_factor: '0.5' } }
} as unknown as ValidatorCollectionItem,
'1month'
)
expect(apy).toBe(25000000)
})

it('should return data from cache if possible', () => {
const data = uptimeModule.maybeQueryUptime('1month')
expect(data).toEqual({
'1': 0.5,
'2': 0.75
})
})

it('should clean data correctly', () => {
uptimeModule.clean()
expect(() => uptimeModule.maybeQueryUptime('1month')).toThrowError(
/Validators not set/
)
})
})
})
})
28 changes: 20 additions & 8 deletions apps/dashboard/src/lib/validators/uptime-module.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
import { BehaviorSubject } from 'rxjs'
import {
calculateApy,
getValidatorUptimeSinceDate,
getValidatorUptimeSinceDate as getValidatorUptimeSinceDateOriginal,
uptimePeriodDefinition,
type UptimeValue
} from '@api/utils/entities/component/validator'
import BigNumber from 'bignumber.js'
import type { ValidatorCollectionItem } from '@common/gateway-sdk'
import { YEARLY_XRD_EMISSIONS } from '@constants'

type ValidatorAddress = string

export type UptimeData = Partial<
Record<UptimeValue, Record<ValidatorAddress, number | undefined>>
>

export const UptimeModule = () => {
export type UptimeModule = ReturnType<typeof UptimeModule>
export const UptimeModule = (
dependencies: {
getValidatorUptimeSinceDate: typeof getValidatorUptimeSinceDateOriginal
} = {
getValidatorUptimeSinceDate: getValidatorUptimeSinceDateOriginal
}
) => {
const { getValidatorUptimeSinceDate } = dependencies
const isLoading = new BehaviorSubject<boolean>(false)
let validators:
| Promise<{ address: string; totalStakeInXRD: BigNumber }[]>
Expand All @@ -35,18 +43,22 @@ export const UptimeModule = () => {
},
getDataForUptime: (uptime: UptimeValue) => uptimeData[uptime] || {},
getApy: (validator: ValidatorCollectionItem, uptime: UptimeValue) => {
if (!totalAmountStaked) {
throw new Error('Total amount staked not set')
if (!totalAmountStaked || totalAmountStaked.isZero()) {
throw new Error('Invalid totalAmountStaked')
}
const address = validator.address
const fee = Number(validator.effective_fee_factor?.current?.fee_factor)
const uptimePercentage = uptimeData[uptime]?.[address]
return calculateApy(fee, uptimePercentage, totalAmountStaked)

return new BigNumber(YEARLY_XRD_EMISSIONS)
.multipliedBy((1 - fee) * (uptimePercentage ?? 0))
.dividedBy(totalAmountStaked)
.toNumber()
},
maybeQueryUptime: async (uptime: UptimeValue | undefined) => {
maybeQueryUptime: (uptime: UptimeValue | undefined) => {
if (!uptime) return
if (!validators) {
throw new Error('Validators not set')
throw new Error('Validators not set. Call `setValidators` first.')
}
if (uptimeData[uptime]) {
return uptimeData[uptime]
Expand Down
12 changes: 0 additions & 12 deletions packages/ui/src/api/utils/entities/component/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
} from '@api/_deprecated/gateway'
import BigNumber from 'bignumber.js'
import { andThen, isNil, map, pick, pipe, prop, reject } from 'ramda'
import { YEARLY_XRD_EMISSIONS } from '@constants'
import { timeToEpoch } from '@utils'
import { ResultAsync } from 'neverthrow'
import {
Expand Down Expand Up @@ -386,17 +385,6 @@ const transformValidators = (
rank: i + 1
}))

export const calculateApy = (
validatorFeeFactor: number,
uptime: number | undefined,
totalAmountStaked: BigNumber
) => {
return new BigNumber(YEARLY_XRD_EMISSIONS)
.multipliedBy((1 - validatorFeeFactor) * (uptime ?? 0))
.dividedBy(totalAmountStaked)
.toNumber()
}

const appendStakeUnits =
<T, K>(validators: ValidatorListItem<T, K>[], ledger_state: LedgerState) =>
async (
Expand Down

0 comments on commit 911c0b4

Please sign in to comment.