Skip to content

Commit

Permalink
feat: Add page for submitting claims (#4382)
Browse files Browse the repository at this point in the history
* chore: Add restitutions page

* chore: Hook up to contract

* chore: Handle multiple tx hashes

* chore: Remove change

* chore: Add blurb

* chore: Update styling

* chore: Add context

* chore: Change to claim submissions

* chore: Update page title

* chore: Update route

* chore: Update wording

* chore: Revert word change

* chore: Update wording and link

* chore: Update wording

* chore: Set mainnet as claims network

* chore: Update network wording

* fix: Btn link
  • Loading branch information
garethfuller authored Oct 26, 2023
1 parent a8f8e5e commit e7880e2
Show file tree
Hide file tree
Showing 12 changed files with 314 additions and 5 deletions.
5 changes: 5 additions & 0 deletions src/components/_global/BalContainer/BalContainer.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<div class="xl:container xl:px-4 pt-10 md:pt-12 xl:mx-auto">
<slot />
</div>
</template>
4 changes: 2 additions & 2 deletions src/components/_global/BalSelectInput/BalSelectInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default defineComponent({
},
props: {
modelValue: { type: String, default: '' },
modelValue: { type: [String, Number], default: '' },
options: { type: Array, required: true },
name: { type: String, required: true },
label: { type: String, default: '' },
Expand Down Expand Up @@ -163,7 +163,7 @@ export default defineComponent({

<style scoped>
.bal-select-input {
@apply relative w-full rounded-lg shadow hover:shadow-none focus:shadow-none overflow-hidden px-2
@apply relative rounded-lg shadow hover:shadow-none focus:shadow-none overflow-hidden px-2
bg-gray-50 dark:bg-gray-800 transition-all;
}
Expand Down
7 changes: 6 additions & 1 deletion src/components/_global/BalVStack/BalVStack.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
alignItemVariants,
cn,
justifyVariants,
maxWidthVariants,
paddingVariants,
widthVariants,
ySpacingVariants,
Expand All @@ -16,6 +17,7 @@ const variants = cva('flex flex-col', {
spacing: ySpacingVariants,
padd: paddingVariants,
width: widthVariants,
maxWidth: maxWidthVariants,
},
});
Expand All @@ -27,11 +29,14 @@ defineProps<{
spacing?: Props['spacing'];
padd?: Props['padd'];
width?: Props['width'];
maxWidth?: Props['maxWidth'];
}>();
</script>

<template>
<div :class="cn(variants({ justify, align, spacing, padd, width }))">
<div
:class="cn(variants({ justify, align, spacing, padd, width, maxWidth }))"
>
<slot />
</div>
</template>
4 changes: 4 additions & 0 deletions src/composables/useNetwork.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ export function getNetworkSlug(network: Network): string {
return config[network].slug;
}

export function getNetworkName(network: Network): string {
return config[network].name;
}

export function networkFromSlug(networkSlug: string): Network | null {
const networkConf = Object.values(config).find((config: Config) => {
return config.slug === networkSlug;
Expand Down
3 changes: 2 additions & 1 deletion src/composables/useTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ export type TransactionAction =
| 'stake'
| 'restake'
| 'sync'
| 'userGaugeCheckpoint';
| 'userGaugeCheckpoint'
| 'claimSubmission';

export type TransactionType = 'order' | 'tx';

Expand Down
1 change: 1 addition & 0 deletions src/lib/config/mainnet/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const contracts: Contracts = {
feeDistributorDeprecated: '0x26743984e3357eFC59f2fd6C1aFDC310335a61c9',
faucet: '',
omniVotingEscrow: '0x96484f2aBF5e58b15176dbF1A799627B53F13B6d',
claimSubmission: '0x70b55Af71B29c5Ca7e67bD1995250364C4bE5554',
};

export default contracts;
1 change: 1 addition & 0 deletions src/lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export interface Contracts {
faucet: string;
gaugeRewardsHelper?: string;
omniVotingEscrow?: string;
claimSubmission?: string;
}

export interface RateProviders {
Expand Down
24 changes: 24 additions & 0 deletions src/lib/utils/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ export const widthVariants = {
px: 'w-px',
};

export const maxWidthVariants = {
none: 'max-w-none',
xs: 'max-w-xs',
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
'2xl': 'max-w-2xl',
'3xl': 'max-w-3xl',
'4xl': 'max-w-4xl',
'5xl': 'max-w-5xl',
'6xl': 'max-w-6xl',
'7xl': 'max-w-7xl',
full: 'max-w-full',
min: 'max-w-min',
max: 'max-w-max',
prose: 'max-w-prose',
'screen-sm': 'max-w-screen-sm',
'screen-md': 'max-w-screen-md',
'screen-lg': 'max-w-screen-lg',
'screen-xl': 'max-w-screen-xl',
'screen-2xl': 'max-w-screen-2xl',
};

export const textSizeVariants = {
xs: 'text-xs',
sm: 'text-sm',
Expand Down
5 changes: 5 additions & 0 deletions src/lib/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,8 @@ export function isGreaterThan(min: number | string, msg = '') {
bnum(v).isGreaterThan(min) ||
(msg ? msg : i18n.global.t('mustBeMoreThan', [min]));
}

export function isTxHash() {
const regex = /^0x([A-Fa-f0-9]{64})$/;
return v => !v || regex.test(v) || 'Must be valid transaction hash';
}
3 changes: 2 additions & 1 deletion src/locales/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -1489,7 +1489,8 @@
"increaseLock": "Increase lock",
"unlock": "Unlock",
"sync": "Sync",
"userGaugeCheckpoint": "Pool gauge veBAL update"
"userGaugeCheckpoint": "Pool gauge veBAL update",
"claimSubmission": "Claim submission"
},
"transactionDeadline": "Transaction deadline",
"transactionDeadlineTooltip": "Your swap will expire and not execute if it is pending for more than the selected duration. Only executed transactions incur fees for swaps between ERC-20 tokens.",
Expand Down
256 changes: 256 additions & 0 deletions src/pages/claim-submissions.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
<script lang="ts" setup>
import { Config, Network } from '@/lib/config/types';
import { isRequired, isTxHash } from '@/lib/utils/validations';
import config from '@/lib/config';
import TxActionBtn from '@/components/btns/TxActionBtn/TxActionBtn.vue';
import useWeb3 from '@/services/web3/useWeb3';
import { TransactionBuilder } from '@/services/web3/transactions/transaction.builder';
import { configService } from '@/services/config/config.service';
import {
networkId,
getNetworkSlug,
getNetworkName,
} from '@/composables/useNetwork';
/**
* TYPES
*/
type Form = {
network: Network;
txHashes: string[];
valid: boolean[];
};
type NetworkOption = {
text: string;
value: Network;
};
/**
* STATE
*/
const claimsNetwork = Network.MAINNET;
const networkOptions: NetworkOption[] = Object.values(config)
.filter(config => config.visibleInUI && !config.testNetwork)
.map(convertConfigToNetworkOption);
const form = reactive<Form>({
network: networkOptions[0].value,
txHashes: [''],
valid: [true],
});
/**
* COMPOSABLES
*/
const {
account,
getSigner,
startConnectWithInjectedProvider,
isMismatchedNetwork,
} = useWeb3();
/**
* COMPUTED
*/
const wrongNetwork = computed(() => networkId.value !== claimsNetwork);
const formValid = computed(() => {
return form.txHashes.every((txHash, index) => {
return form.valid[index];
});
});
/**
* METHODS
*/
function convertConfigToNetworkOption(config: Config): NetworkOption {
return {
text: config.chainName,
value: config.chainId,
};
}
function clearForm() {
form.network = networkOptions[0].value;
form.txHashes = [''];
form.valid = [true];
}
function addHash() {
form.txHashes.push('');
}
function removeHash(index) {
form.txHashes.splice(index, 1);
}
function uniqueTxHash(hashes: string[]) {
return v =>
!v || hashes.filter(h => h === v).length <= 1 || 'Duplicate transaction';
}
async function submitClaim() {
const signer = getSigner();
const contractAddress = configService.network.addresses.claimSubmission;
if (!contractAddress) throw new Error('No contract address found');
const txBuilder = new TransactionBuilder(signer);
return txBuilder.contract.sendTransaction({
contractAddress: contractAddress,
abi: [
'function submitClaim(string memory network, bytes32[] memory txHashes) public',
],
action: 'submitClaim',
params: [form.network.toString(), form.txHashes],
});
}
</script>

<template>
<BalContainer>
<BalVStack>
<h1 class="mb-2">Claim Submission</h1>
<p class="mb-4 max-w-3xl">
This tool was created to facilitate user claim submissions resulting
from the exploit described
<a
class="link"
href="https://medium.com/immunefi/balancer-rounding-error-bugfix-review-cbf69482ee3d"
target="_blank"
>here</a
>.
</p>
<p class="mb-4 max-w-3xl">
In order to calculate the value of your unrecoverable assets, please
<a
class="link"
href="https://app.balancer.fi/#/ethereum/recovery-exit"
target="_blank"
>withdraw from the affected liquidity pools</a
>
and report the withdrawal transaction hash below. The withdrawal needs
to take place after the first incident occurred; block 18004633
(ethereum) or block 108777562 (optimism).
</p>

<p class="max-w-3xl">
If you had positions in multiple pools, report multiple withdrawal
transaction hashes. If you withdrew partially, report those transactions
too. Please file a new report for every chain.
</p>

<div v-if="wrongNetwork" class="mt-8">
<p class="mb-2">
Claim submission are only accepted on
{{ getNetworkName(Network.MAINNET) }}
</p>
<BalBtn
tag="router-link"
:to="{
name: 'claim-submission',
params: { networkSlug: getNetworkSlug(Network.MAINNET) },
}"
color="gradient"
size="sm"
>Claim on Ethereum Mainnet</BalBtn
>
</div>

<BalCard v-else class="mt-8 max-w-lg" title="Claim submission form">
<BalVStack spacing="md">
<BalVStack>
<div>
<BalText>Network</BalText>
<BalSelectInput
v-model="form.network"
name="network"
:options="networkOptions"
class="w-32 h-11"
/>
</div>

<div>
<BalText>Transaction hashes</BalText>
<BalVStack spacing="sm">
<BalHStack
v-for="(_, index) in form.txHashes"
:key="index"
width="full"
spacing="sm"
align="center"
>
<BalTextInput
v-model="form.txHashes[index]"
v-model:isValid="form.valid[index]"
name="txHash"
size="sm"
placeholder="0x..."
:rules="[
isRequired(),
isTxHash(),
uniqueTxHash(form.txHashes),
]"
class="w-full"
/>
<BalBtn
v-if="index === form.txHashes.length - 1"
size="sm"
outline
square
color="blue"
:disabled="
!formValid ||
form.txHashes[index] === '' ||
form.txHashes.length >= 10
"
@click="addHash"
><BalIcon name="plus"
/></BalBtn>
<BalBtn
v-else
size="sm"
outline
square
color="gray"
@click="removeHash(index)"
><BalIcon name="minus"
/></BalBtn>
</BalHStack>
</BalVStack>
</div>
</BalVStack>
<BalAlert
v-if="isMismatchedNetwork"
title="Wrong network"
description="Your wallet is connected to the wrong network."
type="warning"
class="mb-4"
/>
<TxActionBtn
v-if="account"
label="Submit claim"
color="gradient"
size="sm"
:actionFn="submitClaim"
action="claimSubmission"
:summary="`Claim submission: ${form.txHashes.length} tx(s)`"
:confirmingLabel="`Submitting...`"
:disabled="
!formValid || form.txHashes.length === 0 || isMismatchedNetwork
"
@confirmed="clearForm()"
/>
<BalBtn
v-else
color="gradient"
size="sm"
@click="startConnectWithInjectedProvider"
>Connect wallet</BalBtn
>
</BalVStack>
</BalCard>
</BalVStack>
</BalContainer>
</template>
Loading

0 comments on commit e7880e2

Please sign in to comment.