Skip to content

Commit 7b5acd6

Browse files
Merge pull request #139 from valory-xyz/tanya/votes-reset
(govern) feat: add possibility to reset all votes
2 parents addc657 + 25a8a83 commit 7b5acd6

File tree

4 files changed

+153
-43
lines changed

4 files changed

+153
-43
lines changed

apps/govern/components/Contracts/MyVotingWeight/MyVotingWeight.spec.tsx

+4-12
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import '@testing-library/jest-dom';
22
import { render, screen } from '@testing-library/react';
33
import { Allocation } from 'types';
4-
import { useAccount } from 'wagmi';
5-
6-
import { useVotingPower } from 'hooks/index';
74

85
import { MyVotingWeight } from './MyVotingWeight';
96

10-
jest.mock('wagmi', () => ({ useAccount: jest.fn() }));
11-
jest.mock('hooks/index', () => ({ useVotingPower: jest.fn() }));
7+
jest.mock('wagmi', () => ({
8+
useAccount: jest.fn().mockReturnValue({ address: '0x1234', isConnected: true }),
9+
}));
10+
jest.mock('hooks/index', () => ({ useVotingPower: jest.fn().mockReturnValue({ data: '75.05' }) }));
1211
jest.mock('store/index', () => ({
1312
useAppSelector: jest.fn().mockReturnValue({
1413
userVotes: {},
@@ -33,13 +32,6 @@ const MyVotingWeightExample = () => {
3332
};
3433

3534
describe('<MyVotingWeight/>', () => {
36-
beforeEach(() => {
37-
jest.clearAllMocks();
38-
39-
(useVotingPower as jest.Mock).mockReturnValue({ data: '75.05' });
40-
(useAccount as jest.Mock).mockReturnValue({ address: '0x1234', isConnected: true });
41-
});
42-
4335
it('should display title and description', () => {
4436
render(<MyVotingWeightExample />);
4537

apps/govern/components/Contracts/MyVotingWeight/Votes.spec.tsx

+14
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@ import React from 'react';
44

55
import { Votes } from './Votes';
66

7+
jest.mock('wagmi', () => ({
8+
useAccount: jest.fn().mockReturnValue({ address: '0x1234', isConnected: true }),
9+
}));
10+
11+
jest.mock('@wagmi/core', () => ({
12+
readContract: jest.fn(),
13+
readContracts: jest.fn(),
14+
}));
15+
16+
jest.mock('context/Web3ModalProvider', () => ({
17+
queryClient: jest.fn(),
18+
}));
19+
720
jest.mock('store/index', () => ({
21+
useAppDispatch: jest.fn(),
822
useAppSelector: jest.fn().mockReturnValue({
923
lastUserVote: Date.now(),
1024
userVotes: {

apps/govern/components/Contracts/MyVotingWeight/Votes.tsx

+131-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
import { InfoCircleOutlined } from '@ant-design/icons';
2-
import { Button, Flex, Space, Statistic, Table, Tooltip, Typography } from 'antd';
2+
import {
3+
Button,
4+
Flex,
5+
Modal,
6+
notification,
7+
Space,
8+
Statistic,
9+
Table,
10+
Tooltip,
11+
Typography,
12+
} from 'antd';
313
import { ColumnsType } from 'antd/es/table';
414
import { Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
515
import styled from 'styled-components';
@@ -10,7 +20,13 @@ import { CHAIN_NAMES, RETAINER_ADDRESS } from 'libs/util-constants/src';
1020

1121
import { getBytes32FromAddress } from 'common-util/functions/addresses';
1222
import { NextWeekTooltip } from 'components/NextWeekTooltip';
13-
import { useAppSelector } from 'store/index';
23+
import { useAppDispatch, useAppSelector } from 'store/index';
24+
import { Address } from 'viem';
25+
import { useAccount } from 'wagmi';
26+
import { voteForNomineeWeights } from 'common-util/functions';
27+
import { queryClient } from 'context/Web3ModalProvider';
28+
import { INVALIDATE_AFTER_UPDATE_KEYS } from 'common-util/constants/scopeKeys';
29+
import { clearState } from 'store/govern';
1430

1531
const ONE_DAY_IN_MS = 24 * 60 * 60 * 1000;
1632
const TEN_DAYS_IN_MS = 10 * ONE_DAY_IN_MS;
@@ -93,10 +109,18 @@ type VotesProps = {
93109
};
94110

95111
export const Votes = ({ setIsUpdating, setAllocations }: VotesProps) => {
112+
const dispatch = useAppDispatch();
96113
const { stakingContracts } = useAppSelector((state) => state.govern);
97114
const { lastUserVote, userVotes } = useAppSelector((state) => state.govern);
115+
const { address: account } = useAccount();
98116
const [votesBlocked, setVotesBlocked] = useState(false);
99117

118+
const [isResetModalOpen, setIsResetModalOpen] = useState(false);
119+
const [isResetLoading, setIsResetLoading] = useState(false);
120+
121+
const showResetModal = () => setIsResetModalOpen(true);
122+
const closeResetModal = () => setIsResetModalOpen(false);
123+
100124
useEffect(() => {
101125
setVotesBlocked(lastUserVote !== null ? lastUserVote + TEN_DAYS_IN_MS > Date.now() : false);
102126
}, [lastUserVote]);
@@ -119,6 +143,57 @@ export const Votes = ({ setIsUpdating, setAllocations }: VotesProps) => {
119143
);
120144
};
121145

146+
const resetAllWeights = async () => {
147+
if (!account) return;
148+
149+
setIsResetLoading(true);
150+
151+
const nominees: Address[] = [];
152+
const chainIds: number[] = [];
153+
const weights: string[] = [];
154+
155+
Object.keys(userVotes).forEach((address) => {
156+
const contract = stakingContracts.find((contract) => contract.address === address);
157+
if (contract) {
158+
// Set each staking contract's weight to 0
159+
nominees.push(contract.address);
160+
chainIds.push(contract.chainId);
161+
weights.push('0');
162+
} else if (address === getBytes32FromAddress(RETAINER_ADDRESS)) {
163+
// set the retainer's weight to 0
164+
nominees.push(getBytes32FromAddress(RETAINER_ADDRESS));
165+
chainIds.push(1);
166+
weights.push('0');
167+
}
168+
});
169+
170+
// Vote
171+
voteForNomineeWeights({ account, nominees, chainIds, weights })
172+
.then(() => {
173+
closeResetModal();
174+
notification.success({
175+
message: 'Your votes have been reset',
176+
});
177+
178+
// Reset previously saved data so it's re-fetched automatically
179+
queryClient.removeQueries({
180+
predicate: (query) =>
181+
INVALIDATE_AFTER_UPDATE_KEYS.includes(
182+
(query.queryKey[1] as Record<string, string>)?.scopeKey,
183+
),
184+
});
185+
dispatch(clearState());
186+
})
187+
.catch((error) => {
188+
notification.error({
189+
message: error.message,
190+
});
191+
})
192+
.finally(() => {
193+
setIsResetLoading(false);
194+
});
195+
};
196+
122197
const unblockVoting = () => setVotesBlocked(false);
123198
const deadline = lastUserVote ? lastUserVote + TEN_DAYS_IN_MS : undefined;
124199

@@ -157,34 +232,59 @@ export const Votes = ({ setIsUpdating, setAllocations }: VotesProps) => {
157232
}, [userVotes, stakingContracts]);
158233

159234
return (
160-
<VotesRoot>
161-
<Flex gap={16} align="center" justify="end">
162-
{votesBlocked && lastUserVote !== null && (
163-
<Text type="secondary">
164-
<Countdown
165-
prefix={<Text type="secondary">Cooldown period: </Text>}
166-
format={
167-
deadline && deadline < Date.now() + ONE_DAY_IN_MS
168-
? 'H[h] m[m] s[s]'
169-
: 'D[d] H[h] m[m]'
170-
}
171-
value={deadline}
172-
onFinish={unblockVoting}
173-
/>
174-
</Text>
175-
)}
176-
<Button size="large" type="primary" disabled={votesBlocked} onClick={startEditing}>
177-
Update voting weight
178-
</Button>
179-
</Flex>
180-
<Table<MyVote>
181-
className="mt-16"
182-
columns={columns}
183-
dataSource={data}
184-
pagination={false}
185-
rowClassName={rowClassName}
186-
rowKey={(record) => record.address || record.name}
187-
/>
188-
</VotesRoot>
235+
<>
236+
<VotesRoot>
237+
<Flex gap={16} align="center" justify="space-between">
238+
{votesBlocked && lastUserVote !== null && (
239+
<Text type="secondary">
240+
<Countdown
241+
prefix={<Text type="secondary">Cooldown period: </Text>}
242+
format={
243+
deadline && deadline < Date.now() + ONE_DAY_IN_MS
244+
? 'H[h] m[m] s[s]'
245+
: 'D[d] H[h] m[m]'
246+
}
247+
value={deadline}
248+
onFinish={unblockVoting}
249+
/>
250+
</Text>
251+
)}
252+
<Flex gap={8}>
253+
<Button size="large" disabled={votesBlocked} onClick={showResetModal}>
254+
Reset all weights
255+
</Button>
256+
<Button size="large" type="primary" disabled={votesBlocked} onClick={startEditing}>
257+
Update voting weight
258+
</Button>
259+
</Flex>
260+
</Flex>
261+
<Table<MyVote>
262+
className="mt-16"
263+
columns={columns}
264+
dataSource={data}
265+
pagination={false}
266+
rowClassName={rowClassName}
267+
rowKey={(record) => record.address || record.name}
268+
/>
269+
</VotesRoot>
270+
<Modal
271+
title="Reset all weights"
272+
open={isResetModalOpen}
273+
onOk={resetAllWeights}
274+
onCancel={closeResetModal}
275+
cancelText="Cancel"
276+
okText="Reset all weights"
277+
confirmLoading={isResetLoading}
278+
>
279+
<Paragraph>
280+
This will seize all your voting weight, including unallocated ones applied to the Rollover
281+
Pool.
282+
</Paragraph>
283+
<Paragraph>
284+
After you confirm, you’ll enter a 10 day cooldown period. You won&apos;t be able to update
285+
your weights during that time.
286+
</Paragraph>
287+
</Modal>
288+
</>
189289
);
190290
};

apps/govern/jest.setup.js

+4
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,7 @@ jest.mock('wagmi/chains', () => ({
3333
celo,
3434
mode,
3535
}));
36+
37+
jest.mock('common-util/config/wagmi', () => ({
38+
SUPPORTED_CHAINS: [{ name: 'ethereum', chainId: 1 }],
39+
}));

0 commit comments

Comments
 (0)