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(),