diff --git a/src/app/collective-rewards/allocations/context/AllocationsContext.tsx b/src/app/collective-rewards/allocations/context/AllocationsContext.tsx
index 3cd28e250..d8d28607a 100644
--- a/src/app/collective-rewards/allocations/context/AllocationsContext.tsx
+++ b/src/app/collective-rewards/allocations/context/AllocationsContext.tsx
@@ -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'
diff --git a/src/app/collective-rewards/allocations/context/utis.test.ts b/src/app/collective-rewards/allocations/context/utils.test.ts
similarity index 51%
rename from src/app/collective-rewards/allocations/context/utis.test.ts
rename to src/app/collective-rewards/allocations/context/utils.test.ts
index 874970968..bde7a0ff2 100644
--- a/src/app/collective-rewards/allocations/context/utis.test.ts
+++ b/src/app/collective-rewards/allocations/context/utils.test.ts
@@ -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', () => {
@@ -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
+
+ 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([])
+ })
+ })
})
diff --git a/src/app/collective-rewards/allocations/context/utils.ts b/src/app/collective-rewards/allocations/context/utils.ts
index 2ac8a6e9d..50b8b9e74 100644
--- a/src/app/collective-rewards/allocations/context/utils.ts
+++ b/src/app/collective-rewards/allocations/context/utils.ts
@@ -1,5 +1,6 @@
import { Address } from 'viem'
import { Allocations, Backer } from './AllocationsContext'
+import { Builder } from '../../types'
export type ValidateAllocationsStateParams = {
backer: Pick
@@ -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
+ },
+ [[], []],
+ )
+}
diff --git a/src/app/collective-rewards/allocations/hooks/useAllocateVotes.test.tsx b/src/app/collective-rewards/allocations/hooks/useAllocateVotes.test.tsx
new file mode 100644
index 000000000..dbb93ce28
--- /dev/null
+++ b/src/app/collective-rewards/allocations/hooks/useAllocateVotes.test.tsx
@@ -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')
+ })
+ })
+ })
+})
diff --git a/src/app/collective-rewards/allocations/hooks/useAllocateVotes.ts b/src/app/collective-rewards/allocations/hooks/useAllocateVotes.ts
new file mode 100644
index 000000000..2b2affccf
--- /dev/null
+++ b/src/app/collective-rewards/allocations/hooks/useAllocateVotes.ts
@@ -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,
+ }
+}
diff --git a/src/app/collective-rewards/allocations/page.tsx b/src/app/collective-rewards/allocations/page.tsx
index f9d3be010..b3b50dfac 100644
--- a/src/app/collective-rewards/allocations/page.tsx
+++ b/src/app/collective-rewards/allocations/page.tsx
@@ -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,
@@ -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()
@@ -111,12 +72,10 @@ export default function Allocations() {
{/* TODO: review disabled statuses */}
@@ -125,7 +84,6 @@ export default function Allocations() {
onClick={() => onReset()}
textClassName="font-bold text-[18px] text-primary"
>
- {' '}
Reset allocations
diff --git a/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.test.tsx b/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.test.tsx
index cbaf20e40..1d388fab1 100644
--- a/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.test.tsx
+++ b/src/app/collective-rewards/user/components/Button/BecomeABuilderButton.test.tsx
@@ -4,13 +4,11 @@ import { AlertProvider, useAlertContext } from '@/app/providers/AlertProvider'
import { describe, expect, test, vi, beforeEach, afterEach } from 'vitest'
import { cleanup, render, waitFor } from '@testing-library/react'
import { Builder, BuilderStateFlags } from '@/app/collective-rewards/types'
-import { BuilderContextProvider } from '../../context'
+import { BuilderContextProvider } from '@/app/collective-rewards/user'
-vi.mock('@/app/collective-rewards/user/hooks/useGetBuilders', () => {
- return {
- useGetBuilders: vi.fn(),
- }
-})
+vi.mock('@/app/collective-rewards/user/hooks/useGetBuilders', () => ({
+ useGetBuilders: vi.fn(),
+}))
vi.mock('@/app/providers/AlertProvider', () => ({
useAlertContext: vi.fn(),