From 793051871d8c0b9ee37c7ad0814c47c0ce3c6a32 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Fri, 10 Jan 2025 13:57:19 +0100 Subject: [PATCH 1/4] Disable delegation for validators, groups and their signers --- src/config/wagmi.tsx | 4 +- src/features/account/hooks.ts | 65 ++++++++++++++++--- .../delegation/components/DelegateButton.tsx | 13 +++- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/config/wagmi.tsx b/src/config/wagmi.tsx index 166f463a..45a149ad 100644 --- a/src/config/wagmi.tsx +++ b/src/config/wagmi.tsx @@ -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]), }, }); diff --git a/src/features/account/hooks.ts b/src/features/account/hooks.ts index 75539b0f..1d079abd 100644 --- a/src/features/account/hooks.ts +++ b/src/features/account/hooks.ts @@ -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'; @@ -45,16 +46,35 @@ 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) { const { - data: isRegistered, + data: voteSigner, isError, isLoading, error, refetch, } = useReadContract({ + address: Addresses.Accounts, + abi: accountsABI, + functionName: 'voteSignerToAccount', + args: [address || ZERO_ADDRESS], + query: { enabled: !!address }, + }); + + 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, addressOrVoteSigner?: Address) { + const isAccountResult = useReadContract({ address: Addresses.Accounts, abi: accountsABI, functionName: 'isAccount', @@ -62,16 +82,43 @@ export function useAccountDetails(address?: Address) { query: { enabled: !!address }, }); + const isValidatorResult = useReadContract({ + address: Addresses.Validators, + abi: validatorsABI, + functionName: 'isValidator', + args: [addressOrVoteSigner || ZERO_ADDRESS], + query: { enabled: !!addressOrVoteSigner }, + }); + + const isValidatorGroupResult = useReadContract({ + address: Addresses.Validators, + abi: validatorsABI, + functionName: 'isValidatorGroup', + args: [addressOrVoteSigner || ZERO_ADDRESS], + query: { enabled: !!addressOrVoteSigner }, + }); + // 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, + ]), }; } diff --git a/src/features/delegation/components/DelegateButton.tsx b/src/features/delegation/components/DelegateButton.tsx index 0b58f5c0..03ca3fc7 100644 --- a/src/features/delegation/components/DelegateButton.tsx +++ b/src/features/delegation/components/DelegateButton.tsx @@ -2,6 +2,7 @@ import { useMemo } from 'react'; import { SpinnerWithLabel } from 'src/components/animation/Spinner'; import { SolidButton } from 'src/components/buttons/SolidButton'; import { Amount } from 'src/components/numbers/Amount'; +import { useAccountDetails, useVoteSigner } from 'src/features/account/hooks'; import { useDelegateeHistory } from 'src/features/delegation/hooks/useDelegateeHistory'; import { Delegatee } from 'src/features/delegation/types'; import { VotingPower } from 'src/features/governance/components/VotingPower'; @@ -10,10 +11,13 @@ import { TransactionFlowType } from 'src/features/transactions/TransactionFlowTy import { useTransactionModal } from 'src/features/transactions/TransactionModal'; import { bigIntMax } from 'src/utils/math'; import { objLength } from 'src/utils/objects'; +import { useAccount } from 'wagmi'; export function DelegateButton({ delegatee }: { delegatee: Delegatee }) { + const { address } = useAccount(); + const { voteSigner } = useVoteSigner(address); + const { isValidator, isValidatorGroup } = useAccountDetails(address, voteSigner); const { proposalToVotes } = useDelegateeHistory(delegatee.address); - const showTxModal = useTransactionModal(TransactionFlowType.Delegate, { delegatee: delegatee.address, }); @@ -27,8 +31,15 @@ export function DelegateButton({ delegatee }: { delegatee: Delegatee }) {
showTxModal()} >{`️🗳️ Delegate voting power`} + {(isValidator || isValidatorGroup) && ( +

+ Validators and validator groups (as well as their signers) cannot delegate their voting + power. +

+ )} {delegatee.delegatedByPercent > 0 && (

Delegatee is currently delegating{' '} From e6a7620eb5fb7e75ea7fc3388bbab98ae05b97e8 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Fri, 10 Jan 2025 14:55:04 +0100 Subject: [PATCH 2/4] call refetch funcions for the enclosing refetch --- src/features/account/hooks.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/features/account/hooks.ts b/src/features/account/hooks.ts index 1d079abd..b245825f 100644 --- a/src/features/account/hooks.ts +++ b/src/features/account/hooks.ts @@ -115,9 +115,9 @@ export function useAccountDetails(address?: Address, addressOrVoteSigner?: Addre isAccountResult.isLoading || isValidatorResult.isLoading || isValidatorGroupResult.isLoading, refetch: () => Promise.all([ - isAccountResult.refetch, - isValidatorResult.refetch, - isValidatorGroupResult.refetch, + isAccountResult.refetch(), + isValidatorResult.refetch(), + isValidatorGroupResult.refetch(), ]), }; } From ecc109de6ea490b3c9c1a6febee41f385501f0a4 Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Mon, 13 Jan 2025 12:45:14 +0100 Subject: [PATCH 3/4] CR fixes --- src/features/account/hooks.ts | 25 ++++++++----- src/features/delegation/DelegationForm.tsx | 35 +++++++++++++------ .../delegation/components/DelegateButton.tsx | 12 ------- 3 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/features/account/hooks.ts b/src/features/account/hooks.ts index b245825f..e979d324 100644 --- a/src/features/account/hooks.ts +++ b/src/features/account/hooks.ts @@ -82,7 +82,7 @@ export function useAccountDetails(address?: Address, addressOrVoteSigner?: Addre query: { enabled: !!address }, }); - const isValidatorResult = useReadContract({ + const isValidatorOrVoteSignerResult = useReadContract({ address: Addresses.Validators, abi: validatorsABI, functionName: 'isValidator', @@ -90,7 +90,7 @@ export function useAccountDetails(address?: Address, addressOrVoteSigner?: Addre query: { enabled: !!addressOrVoteSigner }, }); - const isValidatorGroupResult = useReadContract({ + const isValidatorGroupOrVoteSignerResult = useReadContract({ address: Addresses.Validators, abi: validatorsABI, functionName: 'isValidatorGroup', @@ -102,22 +102,29 @@ export function useAccountDetails(address?: Address, addressOrVoteSigner?: Addre // as name, metadataUrl, walletAddress, voteSignerToAccount, etc. useToastError( - isAccountResult.error || isValidatorResult.error || isValidatorGroupResult.error, + isAccountResult.error || + isValidatorOrVoteSignerResult.error || + isValidatorGroupOrVoteSignerResult.error, 'Error fetching account details', ); return { isRegistered: isAccountResult.data, - isValidator: isValidatorResult.data, - isValidatorGroup: isValidatorGroupResult.data, - isError: isAccountResult.isError || isValidatorResult.isError || isValidatorGroupResult.isError, + isValidatorOrVoteSigner: isValidatorOrVoteSignerResult.data, + isValidatorGroupOrVoteSigner: isValidatorGroupOrVoteSignerResult.data, + isError: + isAccountResult.isError || + isValidatorOrVoteSignerResult.isError || + isValidatorGroupOrVoteSignerResult.isError, isLoading: - isAccountResult.isLoading || isValidatorResult.isLoading || isValidatorGroupResult.isLoading, + isAccountResult.isLoading || + isValidatorOrVoteSignerResult.isLoading || + isValidatorGroupOrVoteSignerResult.isLoading, refetch: () => Promise.all([ isAccountResult.refetch(), - isValidatorResult.refetch(), - isValidatorGroupResult.refetch(), + isValidatorOrVoteSignerResult.refetch(), + isValidatorGroupOrVoteSignerResult.refetch(), ]), }; } diff --git a/src/features/delegation/DelegationForm.tsx b/src/features/delegation/DelegationForm.tsx index fa653015..94bf717d 100644 --- a/src/features/delegation/DelegationForm.tsx +++ b/src/features/delegation/DelegationForm.tsx @@ -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'; @@ -43,6 +44,8 @@ export function DelegationForm({ const { address } = useAccount(); const { addressToDelegatee } = useDelegatees(); const { delegations, refetch } = useDelegationBalances(address); + const { voteSigner } = useVoteSigner(address); + const { isValidatorOrVoteSigner, isValidatorGroupOrVoteSigner } = useAccountDetails(address, voteSigner); const { getNextTx, txPlanIndex, numTxs, isPlanStarted, onTxSuccess } = useTransactionPlan({ @@ -65,6 +68,7 @@ export function DelegationForm({ const { writeContract, isLoading } = useWriteContractWithReceipt('delegation', onTxSuccess); const isInputDisabled = isLoading || isPlanStarted; + const canDelegate = !isValidatorOrVoteSigner && !isValidatorGroupOrVoteSigner; const onSubmit = (values: DelegateFormValues) => writeContract(getNextTx(values)); @@ -86,7 +90,7 @@ export function DelegationForm({ validateOnBlur={false} > {({ values }) => ( -

+
@@ -113,15 +117,26 @@ export function DelegationForm({ )}
- - {`${toTitleCase(values.action)}`} - + + { + + {`${toTitleCase(values.action)}`} + + } + + {!canDelegate && ( +

+ Validators and validator groups (as well as their signers) cannot delegate their + voting power. +

+ )}
)} diff --git a/src/features/delegation/components/DelegateButton.tsx b/src/features/delegation/components/DelegateButton.tsx index 03ca3fc7..b4087872 100644 --- a/src/features/delegation/components/DelegateButton.tsx +++ b/src/features/delegation/components/DelegateButton.tsx @@ -2,7 +2,6 @@ import { useMemo } from 'react'; import { SpinnerWithLabel } from 'src/components/animation/Spinner'; import { SolidButton } from 'src/components/buttons/SolidButton'; import { Amount } from 'src/components/numbers/Amount'; -import { useAccountDetails, useVoteSigner } from 'src/features/account/hooks'; import { useDelegateeHistory } from 'src/features/delegation/hooks/useDelegateeHistory'; import { Delegatee } from 'src/features/delegation/types'; import { VotingPower } from 'src/features/governance/components/VotingPower'; @@ -11,12 +10,8 @@ import { TransactionFlowType } from 'src/features/transactions/TransactionFlowTy import { useTransactionModal } from 'src/features/transactions/TransactionModal'; import { bigIntMax } from 'src/utils/math'; import { objLength } from 'src/utils/objects'; -import { useAccount } from 'wagmi'; export function DelegateButton({ delegatee }: { delegatee: Delegatee }) { - const { address } = useAccount(); - const { voteSigner } = useVoteSigner(address); - const { isValidator, isValidatorGroup } = useAccountDetails(address, voteSigner); const { proposalToVotes } = useDelegateeHistory(delegatee.address); const showTxModal = useTransactionModal(TransactionFlowType.Delegate, { delegatee: delegatee.address, @@ -31,15 +26,8 @@ export function DelegateButton({ delegatee }: { delegatee: Delegatee }) {
showTxModal()} >{`️🗳️ Delegate voting power`} - {(isValidator || isValidatorGroup) && ( -

- Validators and validator groups (as well as their signers) cannot delegate their voting - power. -

- )} {delegatee.delegatedByPercent > 0 && (

Delegatee is currently delegating{' '} From c2cf6098ff2d31efd3909247065c65ab20feca6c Mon Sep 17 00:00:00 2001 From: Leszek Stachowski Date: Thu, 16 Jan 2025 16:47:39 +0100 Subject: [PATCH 4/4] allow vote signers to delegate votes --- src/features/account/hooks.ts | 39 ++++++++----------- src/features/delegation/DelegationForm.tsx | 12 ++++-- src/features/transactions/TransactionFlow.tsx | 14 ++++--- 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/features/account/hooks.ts b/src/features/account/hooks.ts index e979d324..dafcdb1e 100644 --- a/src/features/account/hooks.ts +++ b/src/features/account/hooks.ts @@ -46,7 +46,7 @@ export function useLockedBalance(address?: Address) { }; } -export function useVoteSigner(address?: Address) { +export function useVoteSigner(address?: Address, isRegistered?: boolean) { const { data: voteSigner, isError, @@ -58,7 +58,7 @@ export function useVoteSigner(address?: Address) { abi: accountsABI, functionName: 'voteSignerToAccount', args: [address || ZERO_ADDRESS], - query: { enabled: !!address }, + query: { enabled: !!address && !!isRegistered }, }); useToastError(error, 'Error fetching vote signer'); @@ -73,7 +73,7 @@ export function useVoteSigner(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, addressOrVoteSigner?: Address) { +export function useAccountDetails(address?: Address) { const isAccountResult = useReadContract({ address: Addresses.Accounts, abi: accountsABI, @@ -82,49 +82,42 @@ export function useAccountDetails(address?: Address, addressOrVoteSigner?: Addre query: { enabled: !!address }, }); - const isValidatorOrVoteSignerResult = useReadContract({ + const isValidatorResult = useReadContract({ address: Addresses.Validators, abi: validatorsABI, functionName: 'isValidator', - args: [addressOrVoteSigner || ZERO_ADDRESS], - query: { enabled: !!addressOrVoteSigner }, + args: [address || ZERO_ADDRESS], + query: { enabled: !!address }, }); - const isValidatorGroupOrVoteSignerResult = useReadContract({ + const isValidatorGroupResult = useReadContract({ address: Addresses.Validators, abi: validatorsABI, functionName: 'isValidatorGroup', - args: [addressOrVoteSigner || ZERO_ADDRESS], - query: { enabled: !!addressOrVoteSigner }, + 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( - isAccountResult.error || - isValidatorOrVoteSignerResult.error || - isValidatorGroupOrVoteSignerResult.error, + isAccountResult.error || isValidatorResult.error || isValidatorGroupResult.error, 'Error fetching account details', ); return { isRegistered: isAccountResult.data, - isValidatorOrVoteSigner: isValidatorOrVoteSignerResult.data, - isValidatorGroupOrVoteSigner: isValidatorGroupOrVoteSignerResult.data, - isError: - isAccountResult.isError || - isValidatorOrVoteSignerResult.isError || - isValidatorGroupOrVoteSignerResult.isError, + isValidator: isValidatorResult.data, + isValidatorGroup: isValidatorGroupResult.data, + isError: isAccountResult.isError || isValidatorResult.isError || isValidatorGroupResult.isError, isLoading: - isAccountResult.isLoading || - isValidatorOrVoteSignerResult.isLoading || - isValidatorGroupOrVoteSignerResult.isLoading, + isAccountResult.isLoading || isValidatorResult.isLoading || isValidatorGroupResult.isLoading, refetch: () => Promise.all([ isAccountResult.refetch(), - isValidatorOrVoteSignerResult.refetch(), - isValidatorGroupOrVoteSignerResult.refetch(), + isValidatorResult.refetch(), + isValidatorGroupResult.refetch(), ]), }; } diff --git a/src/features/delegation/DelegationForm.tsx b/src/features/delegation/DelegationForm.tsx index 94bf717d..77cd0ee7 100644 --- a/src/features/delegation/DelegationForm.tsx +++ b/src/features/delegation/DelegationForm.tsx @@ -44,8 +44,10 @@ export function DelegationForm({ const { address } = useAccount(); const { addressToDelegatee } = useDelegatees(); const { delegations, refetch } = useDelegationBalances(address); - const { voteSigner } = useVoteSigner(address); - const { isValidatorOrVoteSigner, isValidatorGroupOrVoteSigner } = useAccountDetails(address, voteSigner); + 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({ @@ -68,7 +70,11 @@ export function DelegationForm({ const { writeContract, isLoading } = useWriteContractWithReceipt('delegation', onTxSuccess); const isInputDisabled = isLoading || isPlanStarted; - const canDelegate = !isValidatorOrVoteSigner && !isValidatorGroupOrVoteSigner; + const canDelegate = + !isValidator && + !isValidatorGroup && + !isVoteSignerForValidator && + !isVoteSignerForValidatorGroup; const onSubmit = (values: DelegateFormValues) => writeContract(getNextTx(values)); diff --git a/src/features/transactions/TransactionFlow.tsx b/src/features/transactions/TransactionFlow.tsx index e637e52c..778de99f 100644 --- a/src/features/transactions/TransactionFlow.tsx +++ b/src/features/transactions/TransactionFlow.tsx @@ -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'; @@ -25,16 +26,19 @@ export function TransactionFlow({ closeModal, }: TransactionFlowProps & { 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 = Loading account data...; - } else if (!isRegistered) { + } else if (!isRegistered && !isDelegatingAsVoteSigner) { Component = ; - } else if (lockedBalance <= 0n && requiresLockedFunds) { + } else if (lockedBalance <= 0n && requiresLockedFunds && !isDelegatingAsVoteSigner) { Component = ; } else if (!confirmationDetails) { Component = ;