Skip to content

Commit

Permalink
Merge pull request #135 from celo-org/shazarre/Disable_delegation_for…
Browse files Browse the repository at this point in the history
…_validators_and_signers

Disable delegation for validators, groups and their signers
  • Loading branch information
nicolasbrugneaux authored Jan 21, 2025
2 parents a3f9003 + fe413d7 commit dd6a4f9
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 26 deletions.
4 changes: 3 additions & 1 deletion src/config/wagmi.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export const wagmiConfig = createConfig({
chains: [config.chain],
connectors,
transports: {
[celo.id]: fallback([http(config.chain.rpcUrls.default.http[0]), http(infuraRpcUrl)]),
[celo.id]: fallback([http(config.chain.rpcUrls.default.http[0]), http(infuraRpcUrl)], {
rank: true,
}),
[celoAlfajores.id]: http(config.chain.rpcUrls.default.http[0]),
},
});
Expand Down
65 changes: 56 additions & 9 deletions src/features/account/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { accountsABI, lockedGoldABI } from '@celo/abis';
import { validatorsABI } from '@celo/abis-12';
import { useEffect, useState } from 'react';
import { useToastError } from 'src/components/notifications/useToastError';
import { BALANCE_REFRESH_INTERVAL, ZERO_ADDRESS } from 'src/config/consts';
Expand Down Expand Up @@ -45,33 +46,79 @@ export function useLockedBalance(address?: Address) {
};
}

// Note, this retrieves the address' info from the Accounts contract
// It has nothing to do with wallets or backend services
export function useAccountDetails(address?: Address) {
export function useVoteSigner(address?: Address, isRegistered?: boolean) {
const {
data: isRegistered,
data: voteSigner,
isError,
isLoading,
error,
refetch,
} = useReadContract({
address: Addresses.Accounts,
abi: accountsABI,
functionName: 'voteSignerToAccount',
args: [address || ZERO_ADDRESS],
query: { enabled: !!address && !!isRegistered },
});

useToastError(error, 'Error fetching vote signer');

return {
voteSigner,
isError,
isLoading,
refetch,
};
}

// Note, this retrieves the address' info from the Accounts contract
// It has nothing to do with wallets or backend services
export function useAccountDetails(address?: Address) {
const isAccountResult = useReadContract({
address: Addresses.Accounts,
abi: accountsABI,
functionName: 'isAccount',
args: [address || ZERO_ADDRESS],
query: { enabled: !!address },
});

const isValidatorResult = useReadContract({
address: Addresses.Validators,
abi: validatorsABI,
functionName: 'isValidator',
args: [address || ZERO_ADDRESS],
query: { enabled: !!address },
});

const isValidatorGroupResult = useReadContract({
address: Addresses.Validators,
abi: validatorsABI,
functionName: 'isValidatorGroup',
args: [address || ZERO_ADDRESS],
query: { enabled: !!address },
});

// Note, more reads can be added here if more info is needed, such
// as name, metadataUrl, walletAddress, voteSignerToAccount, etc.

useToastError(error, 'Error fetching account registration status');
useToastError(
isAccountResult.error || isValidatorResult.error || isValidatorGroupResult.error,
'Error fetching account details',
);

return {
isRegistered,
isError,
isLoading,
refetch,
isRegistered: isAccountResult.data,
isValidator: isValidatorResult.data,
isValidatorGroup: isValidatorGroupResult.data,
isError: isAccountResult.isError || isValidatorResult.isError || isValidatorGroupResult.isError,
isLoading:
isAccountResult.isLoading || isValidatorResult.isLoading || isValidatorGroupResult.isLoading,
refetch: () =>
Promise.all([
isAccountResult.refetch(),
isValidatorResult.refetch(),
isValidatorGroupResult.refetch(),
]),
};
}

Expand Down
41 changes: 31 additions & 10 deletions src/features/delegation/DelegationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RadioField } from 'src/components/input/RadioField';
import { RangeField } from 'src/components/input/RangeField';
import { TextField } from 'src/components/input/TextField';
import { MAX_NUM_DELEGATEES, ZERO_ADDRESS } from 'src/config/consts';
import { useAccountDetails, useVoteSigner } from 'src/features/account/hooks';
import { getDelegateTxPlan } from 'src/features/delegation/delegatePlan';
import { useDelegatees } from 'src/features/delegation/hooks/useDelegatees';
import { useDelegationBalances } from 'src/features/delegation/hooks/useDelegationBalances';
Expand Down Expand Up @@ -43,6 +44,10 @@ export function DelegationForm({
const { address } = useAccount();
const { addressToDelegatee } = useDelegatees();
const { delegations, refetch } = useDelegationBalances(address);
const { isValidator, isValidatorGroup, isRegistered } = useAccountDetails(address);
const { voteSigner } = useVoteSigner(address, isRegistered);
const { isValidator: isVoteSignerForValidator, isValidatorGroup: isVoteSignerForValidatorGroup } =
useAccountDetails(voteSigner);

const { getNextTx, txPlanIndex, numTxs, isPlanStarted, onTxSuccess } =
useTransactionPlan<DelegateFormValues>({
Expand All @@ -65,6 +70,11 @@ export function DelegationForm({

const { writeContract, isLoading } = useWriteContractWithReceipt('delegation', onTxSuccess);
const isInputDisabled = isLoading || isPlanStarted;
const canDelegate =
!isValidator &&
!isValidatorGroup &&
!isVoteSignerForValidator &&
!isVoteSignerForValidatorGroup;

const onSubmit = (values: DelegateFormValues) => writeContract(getNextTx(values));

Expand All @@ -86,7 +96,7 @@ export function DelegationForm({
validateOnBlur={false}
>
{({ values }) => (
<Form className="mt-4 flex flex-1 flex-col justify-between">
<Form className="mt-4 flex flex-1 flex-col justify-between space-y-3">
<div
className={values.action === DelegateActionType.Transfer ? 'space-y-3' : 'space-y-5'}
>
Expand All @@ -113,15 +123,26 @@ export function DelegationForm({
)}
<PercentField delegations={delegations} disabled={isInputDisabled} />
</div>
<MultiTxFormSubmitButton
txIndex={txPlanIndex}
numTxs={numTxs}
isLoading={isLoading}
loadingText={ActionToVerb[values.action]}
tipText={ActionToTipText[values.action]}
>
{`${toTitleCase(values.action)}`}
</MultiTxFormSubmitButton>

{
<MultiTxFormSubmitButton
txIndex={txPlanIndex}
numTxs={numTxs}
isLoading={isLoading}
loadingText={ActionToVerb[values.action]}
tipText={ActionToTipText[values.action]}
disabled={!canDelegate}
>
{`${toTitleCase(values.action)}`}
</MultiTxFormSubmitButton>
}

{!canDelegate && (
<p className={'min-w-[18rem] max-w-sm text-xs text-red-600'}>
Validators and validator groups (as well as their signers) cannot delegate their
voting power.
</p>
)}
</Form>
)}
</Formik>
Expand Down
1 change: 0 additions & 1 deletion src/features/delegation/components/DelegateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { objLength } from 'src/utils/objects';

export function DelegateButton({ delegatee }: { delegatee: Delegatee }) {
const { proposalToVotes } = useDelegateeHistory(delegatee.address);

const showTxModal = useTransactionModal(TransactionFlowType.Delegate, {
delegatee: delegatee.address,
});
Expand Down
14 changes: 9 additions & 5 deletions src/features/transactions/TransactionFlow.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { FunctionComponent, ReactNode, useCallback, useState } from 'react';
import { SpinnerWithLabel } from 'src/components/animation/Spinner';
import { AccountRegisterForm } from 'src/features/account/AccountRegisterForm';
import { useAccountDetails, useLockedBalance } from 'src/features/account/hooks';
import { useAccountDetails, useLockedBalance, useVoteSigner } from 'src/features/account/hooks';
import { DelegationForm } from 'src/features/delegation/DelegationForm';
import { LockForm } from 'src/features/locking/LockForm';
import { TransactionConfirmation } from 'src/features/transactions/TransactionConfirmation';
import { ConfirmationDetails, OnConfirmedFn } from 'src/features/transactions/types';
Expand All @@ -25,16 +26,19 @@ export function TransactionFlow<FormDefaults extends {}>({
closeModal,
}: TransactionFlowProps<FormDefaults> & { closeModal: () => void }) {
const { address } = useAccount();
const { lockedBalance } = useLockedBalance(address);
const { isRegistered, refetch: refetchAccountDetails } = useAccountDetails(address);
const { voteSigner, isLoading: isVoteSignerLoading } = useVoteSigner(address, isRegistered);
const { lockedBalance } = useLockedBalance(address);
const { confirmationDetails, onConfirmed } = useTransactionFlowConfirmation();
const isDelegatingAsVoteSigner =
FormComponent.name === DelegationForm.name && voteSigner !== address;

let Component: ReactNode;
if (!address || isNullish(lockedBalance) || isNullish(isRegistered)) {
if (!address || isNullish(lockedBalance) || isNullish(isRegistered) || isVoteSignerLoading) {
Component = <SpinnerWithLabel className="py-20">Loading account data...</SpinnerWithLabel>;
} else if (!isRegistered) {
} else if (!isRegistered && !isDelegatingAsVoteSigner) {
Component = <AccountRegisterForm refetchAccountDetails={refetchAccountDetails} />;
} else if (lockedBalance <= 0n && requiresLockedFunds) {
} else if (lockedBalance <= 0n && requiresLockedFunds && !isDelegatingAsVoteSigner) {
Component = <LockForm showTip={true} />;
} else if (!confirmationDetails) {
Component = <FormComponent defaultFormValues={defaultFormValues} onConfirmed={onConfirmed} />;
Expand Down

0 comments on commit dd6a4f9

Please sign in to comment.