Skip to content

Commit

Permalink
refactor: votes allocation
Browse files Browse the repository at this point in the history
  • Loading branch information
franciscotobar committed Jan 14, 2025
1 parent 4971bdd commit 9c86ca0
Show file tree
Hide file tree
Showing 7 changed files with 267 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from '@/app/collective-rewards/allocations/hooks'
import { Builder } from '@/app/collective-rewards/types'
import { createContext, FC, ReactNode, useEffect, useMemo, useState, useCallback } from 'react'
import { Address, zeroAddress } from 'viem'
import { Address } from 'viem'
import { useAccount } from 'wagmi'
import { createActions } from './allocationsActions'
import { useGetBackerRewards } from '../hooks/useBuildersWithBackerRewardPercentage'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, expect, it } from 'vitest'
import { Allocations, Backer } from './AllocationsContext'
import { validateAllocationsState, ValidateAllocationsStateParams } from './utils'
import { getVoteAllocations, validateAllocationsState, ValidateAllocationsStateParams } from './utils'
import { get } from 'http'
import { Address } from 'viem'
import { Builder } from '../../types'

describe('Allocations context utils', () => {
describe('validateAllocationsState', () => {
Expand Down Expand Up @@ -80,4 +83,83 @@ describe('Allocations context utils', () => {
expect(isValidState).toBe(false)
})
})

describe('getVoteAllocations', () => {
const builders = {
'0x1': {
gauge: '0x11',
},
'0x2': {
gauge: '0x21',
},
'0x3': {
gauge: '0x31',
},
} as unknown as Record<Address, Builder>

const getBuilder = (address: Address) => builders[address]

it('should return the modified builders', () => {
const [gauges, allocs] = getVoteAllocations({
initialAllocations: {
'0x1': 3n,
},
currentAllocations: {
'0x1': 5n,
'0x2': 5n,
},
getBuilder,
})

expect(gauges).toEqual(['0x11', '0x21'])
expect(allocs).toEqual([5n, 5n])
})

it('should return the modified builders if the initial allocation changed', () => {
const [gauges, allocs] = getVoteAllocations({
initialAllocations: {
'0x1': 3n,
'0x2': 5n,
},
currentAllocations: {
'0x1': 5n,
'0x2': 5n,
},
getBuilder,
})

expect(gauges).toEqual(['0x11'])
expect(allocs).toEqual([5n])
})

it('should return the empty if there are no modified builders', () => {
const [gauges, allocs] = getVoteAllocations({
initialAllocations: {
'0x2': 5n,
},
currentAllocations: {
'0x2': 5n,
},
getBuilder,
})

expect(gauges).toEqual([])
expect(allocs).toEqual([])
})

it('should return the empty if builder does not exist', () => {
const [gauges, allocs] = getVoteAllocations({
initialAllocations: {
'0x4': 5n,
},
currentAllocations: {
'0x4': 4n,
},
getBuilder,
})

expect(gauges).toEqual([])
expect(allocs).toEqual([])
})
})
})
27 changes: 27 additions & 0 deletions src/app/collective-rewards/allocations/context/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Address } from 'viem'
import { Allocations, Backer } from './AllocationsContext'
import { Builder } from '../../types'

export type ValidateAllocationsStateParams = {
backer: Pick<Backer, 'amountToAllocate' | 'balance' | 'cumulativeAllocation'>
Expand Down Expand Up @@ -27,3 +28,29 @@ export const validateAllocationsState = ({

return true
}

export type GetVoteAllocationsParams = {
initialAllocations: Allocations
currentAllocations: Allocations
getBuilder: (address: Address) => Builder | null
}
export const getVoteAllocations = ({
initialAllocations,
currentAllocations,
getBuilder,
}: GetVoteAllocationsParams) => {
return Object.entries(currentAllocations).reduce<[Address[], bigint[]]>(
(acc, [key, value]) => {
const builderAddress = key as Address
if (value !== initialAllocations[builderAddress]) {
const gauge = getBuilder(builderAddress)?.gauge
if (gauge) {
acc[0].push(gauge)
acc[1].push(value)
}
}
return acc
},
[[], []],
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'
import { useAllocateVotes } from './useAllocateVotes'
import {
useWaitForTransactionReceipt,
UseWaitForTransactionReceiptReturnType,
useWriteContract,
UseWriteContractReturnType,
} from 'wagmi'
import { renderHook } from '@testing-library/react'
import { useContext } from 'react'
import { getVoteAllocations } from '../context/utils'

vi.mock(import('wagmi'), async importOriginal => {
const actual = await importOriginal()
return {
...actual,
useWriteContract: vi.fn(),
useWaitForTransactionReceipt: vi.fn(),
}
})

vi.mock(import('react'), async importOriginal => {
const actual = await importOriginal()
return {
...actual,
useContext: vi.fn(),
}
})

vi.mock('@/app/collective-rewards/allocations/context/utils', () => ({
getVoteAllocations: vi.fn(),
}))

describe('useAllocateVotes', () => {
beforeEach(() => {
vi.mocked(useWaitForTransactionReceipt).mockReturnValue({} as UseWaitForTransactionReceiptReturnType)
})

describe('saveAllocations', () => {
const writeContractAsyncSpy = vi.fn()

beforeEach(() => {
vi.mocked(useContext).mockReturnValue({
initialState: {},
state: {},
})

vi.mocked(useWriteContract).mockReturnValue({
writeContractAsync: writeContractAsyncSpy,
} as unknown as UseWriteContractReturnType)
})

afterEach(() => {
writeContractAsyncSpy.mockClear()
})

test('should return allocate function', () => {
vi.mocked(getVoteAllocations).mockReturnValue([['0x123'], [1n]])

renderHook(async () => {
const { saveAllocations } = useAllocateVotes()

expect(typeof saveAllocations).toBe('function')

saveAllocations()

expect(writeContractAsyncSpy).toHaveBeenCalled()
expect(writeContractAsyncSpy.mock.calls[0][0].functionName).toBe('allocate')
})
})

test('should return allocateBatch function', () => {
vi.mocked(getVoteAllocations).mockReturnValue([
['0x123', '0x456'],
[1n, 3n],
])

renderHook(async () => {
const { saveAllocations } = useAllocateVotes()

expect(typeof saveAllocations).toBe('function')

saveAllocations()

expect(writeContractAsyncSpy).toHaveBeenCalled()
expect(writeContractAsyncSpy.mock.calls[0][0].functionName).toBe('allocateBatch')
})
})
})
})
58 changes: 58 additions & 0 deletions src/app/collective-rewards/allocations/hooks/useAllocateVotes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useWaitForTransactionReceipt, useWriteContract } from 'wagmi'
import { useAwaitedTxReporting } from '@/app/collective-rewards/shared/hooks'
import { BackersManagerAbi } from '@/lib/abis/v2/BackersManagerAbi'
import { BackersManagerAddress } from '@/lib/contracts'
import { useContext } from 'react'
import { AllocationsContext } from '../context'
import { getVoteAllocations } from '../context/utils'

export const useAllocateVotes = () => {
const { writeContractAsync, error: executionError, data: hash, isPending } = useWriteContract()

const {
initialState: { allocations: initialAllocations },
state: { allocations, getBuilder, isValidState },
} = useContext(AllocationsContext)

const { isLoading, isSuccess, data, error: receiptError } = useWaitForTransactionReceipt({ hash })

const error = executionError ?? receiptError

const saveAllocations = () => {
const [gauges, allocs] = getVoteAllocations({
initialAllocations,
currentAllocations: allocations,
getBuilder,
})

const isBatchAllocation = allocs.length > 1

return writeContractAsync({
abi: BackersManagerAbi,
address: BackersManagerAddress,
functionName: isBatchAllocation ? 'allocateBatch' : 'allocate',
args: isBatchAllocation ? [gauges, allocs] : [gauges[0], allocs[0]],
})
}

useAwaitedTxReporting({
hash,
error,
isPendingTx: isPending,
isLoadingReceipt: isLoading,
isSuccess,
receipt: data,
title: 'Saving allocations',
errorContent: 'Error saving allocations',
})

return {
isValidState,
saveAllocations: () => saveAllocations(),
error,
isPendingTx: isPending,
isLoadingReceipt: isLoading,
isSuccess,
receipt: data,
}
}
50 changes: 4 additions & 46 deletions src/app/collective-rewards/allocations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,9 @@
import { Button } from '@/components/Button'
import { MainContainer } from '@/components/MainContainer/MainContainer'
import { Typography } from '@/components/Typography'
import { BackersManagerAbi } from '@/lib/abis/v2/BackersManagerAbi'
import { BackersManagerAddress } from '@/lib/contracts'
import { useRouter } from 'next/navigation'
import { useCallback, useContext, useState } from 'react'
import { Address } from 'viem'
import { useWaitForTransactionReceipt, useWriteContract } from 'wagmi'
import { useAwaitedTxReporting } from '../shared'
import { Builder } from '../types'
import {
AllocationAmount,
Expand All @@ -19,53 +15,18 @@ import {
Header,
} from './components'
import { AllocationsContext } from './context'
import { useAllocateVotes } from './hooks/useAllocateVotes'

export default function Allocations() {
const { writeContractAsync, error: executionError, data: hash, isPending } = useWriteContract()
const { isLoading, isSuccess, data, error: receiptError } = useWaitForTransactionReceipt({ hash })
const [resetCounter, setResetCounter] = useState(0)

const error = executionError || receiptError

useAwaitedTxReporting({
hash,
error,
isPendingTx: isPending,
isLoadingReceipt: isLoading,
isSuccess,
receipt: data,
title: 'Saving allocations',
errorContent: 'Error saving allocations',
})

const router = useRouter()
const {
state: { allocations, getBuilder, isValidState },
state: { allocations, getBuilder },
actions: { resetAllocations },
} = useContext(AllocationsContext)

const saveAllocations = () => {
const [gauges, allocs] = Object.entries(allocations).reduce(
(acc, [key, value]) => {
const builderAddress = key as Address
const gauge = getBuilder(builderAddress)?.gauge
if (gauge) {
acc[0] = [...acc[0], gauge]
acc[1] = [...acc[1], value]
}

return acc
},
[[], [], []] as [Address[], bigint[], Address[]],
)

return writeContractAsync({
abi: BackersManagerAbi,
address: BackersManagerAddress,
functionName: 'allocateBatch',
args: [gauges, allocs],
})
}
const { saveAllocations, isValidState } = useAllocateVotes()

const onReset = useCallback(() => {
resetAllocations()
Expand Down Expand Up @@ -111,12 +72,10 @@ export default function Allocations() {
<div className="flex gap-4">
{/* TODO: review disabled statuses */}
<Button disabled={!isValidState()} variant="primary" onClick={() => saveAllocations()}>
{' '}
Save allocations
</Button>
<Button variant="secondary" onClick={() => cancel()}>
{' '}
Cancel{' '}
Cancel
</Button>
</div>

Expand All @@ -125,7 +84,6 @@ export default function Allocations() {
onClick={() => onReset()}
textClassName="font-bold text-[18px] text-primary"
>
{' '}
Reset allocations
</Button>
</div>
Expand Down
Loading

0 comments on commit 9c86ca0

Please sign in to comment.