Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: infinit loop issue in Edit pool FS, negative number at unstake #1682

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface Props {
changes: ChangesProps | undefined;
}

export default function Edit ({ api, chain, changes, onClose, pool, setChanges, setStep }: Props): React.ReactElement {
function Edit ({ api, chain, changes, onClose, pool, setChanges, setStep }: Props): React.ReactElement {
const { t } = useTranslation();
const theme = useTheme();
const { hierarchy } = useContext(AccountContext);
Expand Down Expand Up @@ -117,7 +117,7 @@ export default function Edit ({ api, chain, changes, onClose, pool, setChanges,

const nextBtnDisable = changes && Object.values(changes).every((value) => {
if (typeof value === 'object' && value !== null) {
return Object.values(value as { [s: string]: unknown }).every((nestedValue) => nestedValue === undefined);
return Object.values(value as Record<string, unknown>).every((nestedValue) => nestedValue === undefined);
}

return value === undefined;
Expand Down Expand Up @@ -255,3 +255,5 @@ export default function Edit ({ api, chain, changes, onClose, pool, setChanges,
</>
);
}

export default React.memo(Edit);
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import type { ChangesProps } from '.';

import { Divider, Grid, Typography } from '@mui/material';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

Check failure on line 16 in packages/extension-polkagate/src/fullscreen/stake/pool/commonTasks/editPool/Review.tsx

View workflow job for this annotation

GitHub Actions / build

'useMemo' is declared but its value is never read.

Check failure on line 16 in packages/extension-polkagate/src/fullscreen/stake/pool/commonTasks/editPool/Review.tsx

View workflow job for this annotation

GitHub Actions / build

'useMemo' is declared but its value is never read.

Check failure on line 16 in packages/extension-polkagate/src/fullscreen/stake/pool/commonTasks/editPool/Review.tsx

View workflow job for this annotation

GitHub Actions / build

'useMemo' is declared but its value is never read.

import SelectProxyModal2 from '@polkadot/extension-polkagate/src/fullscreen/governance/components/SelectProxyModal2';
import { PROXY_TYPE } from '@polkadot/extension-polkagate/src/util/constants';
Expand All @@ -37,89 +37,115 @@
step: number;
}

export default function Review ({ address, api, chain, changes, formatted, pool, setRefresh, setStep, setTxInfo, step }: Props): React.ReactElement {
const { t } = useTranslation();
const proxies = useProxies(api, formatted);
const getRole = (role: string | undefined | null) => {
if (role === undefined) {
return 'Noop';
} else if (role === null) {
return 'Remove';
} else {
return { set: role };
}
};

const [selectedProxy, setSelectedProxy] = useState<Proxy | undefined>();
const [proxyItems, setProxyItems] = useState<ProxyItem[]>();
const [isPasswordError, setIsPasswordError] = useState(false);
const [estimatedFee, setEstimatedFee] = useState<Balance>();
const usePoolTransactionPreparation = (api: ApiPromise | undefined, changes: ChangesProps | undefined, pool: MyPoolInfo, formatted: string, maybeCurrentCommissionPayee?: string) => {
const [inputs, setInputs] = useState<StakingInputs>();

const selectedProxyAddress = selectedProxy?.delegate as unknown as string;

//@ts-ignore
const maybeCurrentCommissionPayee = pool?.bondedPool?.commission?.current?.[1]?.toString() as string | undefined;

useEffect((): void => {
const fetchedProxyItems = proxies?.map((p: Proxy) => ({ proxy: p, status: 'current' })) as ProxyItem[];

setProxyItems(fetchedProxyItems);
}, [proxies]);

const extraInfo = useMemo(() => ({
action: 'Pool Staking',
fee: String(estimatedFee || 0),
subAction: 'Edit Pool'
}), [estimatedFee]);
const [estimatedFee, setEstimatedFee] = useState<Balance>();

useEffect(() => {
if (!api || !changes) {
return;
}

const batchAll = api.tx['utility']['batchAll'];
const setMetadata = api.tx['nominationPools']['setMetadata'];
const updateRoles = api.tx['nominationPools']['updateRoles'];
const setCommission = api.tx['nominationPools']['setCommission'];
const { nominationPools: { setCommission, setMetadata, updateRoles }, utility: { batchAll } } = api.tx;

const txs: { call: SubmittableExtrinsicFunction<'promise', AnyTuple>, params: unknown[] }[] = [];

const getRole = (role: string | undefined | null) => {
if (role === undefined) {
return 'Noop';
} else if (role === null) {
return 'Remove';
} else {
return { set: role };
}
};

changes.newPoolName !== undefined &&
txs.push({ call: setMetadata, params: [pool.poolId, changes?.newPoolName] });
if (changes.newPoolName !== undefined) {
txs.push({ call: setMetadata, params: [pool.poolId, changes.newPoolName] });
}

changes.newRoles !== undefined && !Object.values(changes.newRoles).every((value) => value === undefined) &&
txs.push({ call: updateRoles, params: [pool.poolId, getRole(changes.newRoles.newRoot), getRole(changes.newRoles.newNominator), getRole(changes.newRoles.newBouncer)] });
if (changes.newRoles && !Object.values(changes.newRoles).every((value) => value === undefined)) {
txs.push({
call: updateRoles,
params: [
pool.poolId,
getRole(changes.newRoles?.newRoot),
getRole(changes.newRoles?.newNominator),
getRole(changes.newRoles?.newBouncer)
]
});
}

changes.commission !== undefined && (changes.commission.value !== undefined || changes.commission.payee) &&
txs.push({ call: setCommission, params: [pool.poolId, [(changes.commission.value || 0) * 10 ** 7, changes.commission.payee || maybeCurrentCommissionPayee]] });
if (changes.commission &&
(changes.commission.value !== undefined || changes.commission.payee)) {
txs.push({
call: setCommission,
params: [
pool.poolId,
[
(changes.commission.value || 0) * 10 ** 7,
changes.commission.payee || maybeCurrentCommissionPayee
]
]
});
}

const call = txs.length > 1 ? batchAll : txs[0].call;
const params = txs.length > 1
? [txs.map(({ call, params }) => call(...params))]
: txs[0].params;

let fee: Balance = api.createType('Balance', BN_ONE) as Balance;

// Estimate transaction fee
if (!api?.call?.['transactionPaymentApi']) {
fee = api.createType('Balance', BN_ONE) as Balance;
setEstimatedFee(fee);
}

call(...params).paymentInfo(formatted)
.then((i) => {
fee = api.createType('Balance', i?.partialFee) as Balance;
setEstimatedFee(fee);
})
.catch(console.error);

const extraInfo = {
action: 'Pool Staking',
fee: String(fee ?? 0),
subAction: 'Edit Pool'
};
Comment on lines +113 to +117
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure extraInfo uses the updated fee value

The fee used in extraInfo may not reflect the actual estimated fee because fee estimation is asynchronous. Assign extraInfo after the fee estimation completes to ensure accurate information.

Modify the code to set extraInfo after fee estimation:

-const extraInfo = {
-  action: 'Pool Staking',
-  fee: String(fee ?? 0),
-  subAction: 'Edit Pool'
-};

...

call(...params).paymentInfo(formatted)
  .then((i) => {
    fee = api.createType('Balance', i?.partialFee) as Balance;
    setEstimatedFee(fee);

+   const extraInfo = {
+     action: 'Pool Staking',
+     fee: String(fee ?? 0),
+     subAction: 'Edit Pool'
+   };

+   setInputs({
+     call,
+     extraInfo,
+     params
+   });
  })
  .catch(console.error);

...

-setInputs({
-  call,
-  extraInfo,
-  params
-});

Committable suggestion skipped: line range outside the PR's diff.


setInputs({
call,
extraInfo,
params
});
}, [api, changes, extraInfo, maybeCurrentCommissionPayee, pool.poolId]);
}, [api, changes, pool, formatted, maybeCurrentCommissionPayee]);

useEffect(() => {
if (!api || !inputs?.call) {
return;
}
return { estimatedFee, inputs };
};

if (!api?.call?.['transactionPaymentApi']) {
return setEstimatedFee(api.createType('Balance', BN_ONE) as Balance);
}
function Review ({ address, api, chain, changes, formatted, pool, setRefresh, setStep, setTxInfo, step }: Props): React.ReactElement {
const { t } = useTranslation();
const proxies = useProxies(api, formatted);

inputs.call(...inputs.params)?.paymentInfo(formatted).then((i) => {
setEstimatedFee(api.createType('Balance', i?.partialFee) as Balance);
}).catch(console.error);
}, [api, formatted, inputs]);
const [selectedProxy, setSelectedProxy] = useState<Proxy | undefined>();
const [proxyItems, setProxyItems] = useState<ProxyItem[]>();
const [isPasswordError, setIsPasswordError] = useState(false);

const selectedProxyAddress = selectedProxy?.delegate as unknown as string;

//@ts-ignore
const maybeCurrentCommissionPayee = pool?.bondedPool?.commission?.current?.[1]?.toString() as string | undefined;
Comment on lines +139 to +140
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove @ts-ignore and fix type assertion

The @ts-ignore comment suggests a potential type safety issue. Consider properly typing the pool structure.

Replace with proper type checking:

- //@ts-ignore
- const maybeCurrentCommissionPayee = pool?.bondedPool?.commission?.current?.[1]?.toString() as string | undefined;
+ const maybeCurrentCommissionPayee = pool?.bondedPool?.commission?.current && 
+   Array.isArray(pool.bondedPool.commission.current) && 
+   pool.bondedPool.commission.current[1]?.toString();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
//@ts-ignore
const maybeCurrentCommissionPayee = pool?.bondedPool?.commission?.current?.[1]?.toString() as string | undefined;
const maybeCurrentCommissionPayee = pool?.bondedPool?.commission?.current &&
Array.isArray(pool.bondedPool.commission.current) &&
pool.bondedPool.commission.current[1]?.toString();


useEffect((): void => {
const fetchedProxyItems = proxies?.map((p: Proxy) => ({ proxy: p, status: 'current' })) as ProxyItem[];

setProxyItems(fetchedProxyItems);
}, [proxies]);

const { estimatedFee, inputs } = usePoolTransactionPreparation(api, changes, pool, formatted,maybeCurrentCommissionPayee);

const closeProxy = useCallback(() => setStep(STEPS.REVIEW), [setStep]);

Expand Down Expand Up @@ -149,7 +175,7 @@
<Grid alignItems='center' container direction='column' justifyContent='center' sx={{ m: 'auto', pt: '8px', width: '90%' }}>
<Infotip showQuestionMark text={changes?.newPoolName}>
<Typography fontSize='16px' fontWeight={300} lineHeight='23px'>
{t<string>('Pool name')}
{t('Pool name')}
</Typography>
</Infotip>
<Typography fontSize='25px' fontWeight={400} lineHeight='42px' maxWidth='100%' overflow='hidden' textOverflow='ellipsis' whiteSpace='nowrap'>
Expand All @@ -164,23 +190,23 @@
<ShowPoolRole
chain={chain}
roleAddress={changes?.newRoles?.newRoot as string}
roleTitle={t<string>('Root')}
roleTitle={t('Root')}
showDivider
/>
}
{changes?.newRoles?.newNominator !== undefined &&
<ShowPoolRole
chain={chain}
roleAddress={changes?.newRoles?.newNominator}
roleTitle={t<string>('Nominator')}
roleTitle={t('Nominator')}
showDivider
/>
}
{changes?.newRoles?.newBouncer !== undefined &&
<ShowPoolRole
chain={chain}
roleAddress={changes?.newRoles?.newBouncer}
roleTitle={t<string>('Bouncer')}
roleTitle={t('Bouncer')}
showDivider
/>
}
Expand All @@ -201,7 +227,7 @@
<ShowPoolRole
chain={chain}
roleAddress={changes.commission.payee || maybeCurrentCommissionPayee}
roleTitle={t<string>('Commission payee')}
roleTitle={t('Commission payee')}
showDivider
/>
}
Expand All @@ -218,13 +244,13 @@
<SignArea2
address={address}
call={inputs?.call}
extraInfo={extraInfo}
extraInfo={inputs?.extraInfo}
isPasswordError={isPasswordError}
onSecondaryClick={onBackClick}
params={inputs?.params}
primaryBtnText={t<string>('Confirm')}
primaryBtnText={t('Confirm')}
proxyTypeFilter={PROXY_TYPE.NOMINATION_POOLS}
secondaryBtnText={t<string>('Back')}
secondaryBtnText={t('Back')}
selectedProxy={selectedProxy}
setIsPasswordError={setIsPasswordError}
setRefresh={setRefresh}
Expand Down Expand Up @@ -252,3 +278,5 @@
</Grid>
);
}

export default React.memo(Review);
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface Props {
chain: Chain | null | undefined;
}

export default function ShowPoolRole ({ chain, roleAddress, roleTitle, showDivider }: Props) {
function ShowPoolRole ({ chain, roleAddress, roleTitle, showDivider }: Props) {
const { t } = useTranslation();

return (
Expand All @@ -41,7 +41,7 @@ export default function ShowPoolRole ({ chain, roleAddress, roleTitle, showDivid
/>
</Grid>
: <Typography fontSize='20px' fontWeight={300} lineHeight='23px'>
{t<string>('To be Removed')}
{t('To be Removed')}
</Typography>
}
{showDivider &&
Expand All @@ -50,3 +50,5 @@ export default function ShowPoolRole ({ chain, roleAddress, roleTitle, showDivid
</Grid>
);
}

export default React.memo(ShowPoolRole);
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface ChangesProps {
} | undefined
}

export default function ManageEditPool ({ address, api, chain, onClose, pool, setRefresh }: Props): React.ReactElement {
function ManageEditPool ({ address, api, chain, onClose, pool, setRefresh }: Props): React.ReactElement {
const { t } = useTranslation();
const formatted = useFormatted(address);

Expand Down Expand Up @@ -109,3 +109,5 @@ export default function ManageEditPool ({ address, api, chain, onClose, pool, se
</>
);
}

export default React.memo(ManageEditPool);
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function Unstake ({ address, setRefresh, setShow, show }: Props):
}, [amountAsBN, staked, unstakeMaxAmount]);

const unlockingLen = myPool?.stashIdAccount?.stakingLedger?.unlocking?.length;
const maxUnlockingChunks = api && (api.consts['staking']['maxUnlockingChunks'] as any)?.toNumber();
const maxUnlockingChunks = api?.consts['staking']['maxUnlockingChunks']?.toPrimitive() as unknown as number;
const isPoolRoot = useMemo(() => String(formatted) === String(myPool?.bondedPool?.roles?.root), [formatted, myPool?.bondedPool?.roles?.root]);
const isPoolDepositor = useMemo(() => String(formatted) === String(myPool?.bondedPool?.roles?.depositor), [formatted, myPool?.bondedPool?.roles?.depositor]);
const poolState = useMemo(() => String(myPool?.bondedPool?.state), [myPool?.bondedPool?.state]);
Expand Down Expand Up @@ -131,7 +131,7 @@ export default function Unstake ({ address, setRefresh, setShow, show }: Props):
}, [amountAsBN, api, poolConsts, decimal, staked, t, unstakeMaxAmount, isPoolDepositor, poolMemberCounter, poolState, token]);

useEffect(() => {
if (!api) {
if (!api || !formatted || !unbonded || !amountAsBN) {
return;
}

Expand All @@ -141,23 +141,23 @@ export default function Unstake ({ address, setRefresh, setShow, show }: Props):
return setEstimatedFee(api?.createType('Balance', BN_ONE) as Balance);
}

// eslint-disable-next-line no-void
poolWithdrawUnbonded && maxUnlockingChunks && unlockingLen !== undefined && unbonded && formatted && void unbonded(...params).paymentInfo(formatted).then((i) => {
const fee = i?.partialFee;

if (unlockingLen < maxUnlockingChunks) {
setEstimatedFee(fee);
} else {
const dummyParams = [1, 1];

poolWithdrawUnbonded(...dummyParams)
.paymentInfo(formatted)
.then(
(j) => setEstimatedFee(api.createType('Balance', fee.add(j?.partialFee || BN_ZERO)) as Balance)
)
.catch(console.error);
}
}).catch(console.error);
poolWithdrawUnbonded && maxUnlockingChunks && unlockingLen !== undefined &&
unbonded(...params).paymentInfo(formatted).then((i) => {
const fee = i?.partialFee;

if (unlockingLen < maxUnlockingChunks) {
setEstimatedFee(fee);
} else {
const dummyParams = [1, 1];

poolWithdrawUnbonded(...dummyParams)
.paymentInfo(formatted)
.then(
(j) => setEstimatedFee(api.createType('Balance', fee.add(j?.partialFee || BN_ZERO)) as Balance)
)
.catch(console.error);
}
}).catch(console.error);
}, [amountAsBN, api, decimal, formatted, maxUnlockingChunks, poolWithdrawUnbonded, unbonded, unlockingLen]);

const onChangeAmount = useCallback((value: string) => {
Expand Down Expand Up @@ -191,6 +191,13 @@ export default function Unstake ({ address, setRefresh, setShow, show }: Props):

setUnstakeMaxAmount(false);

if (partial.isNeg()) { // This rare condition occurs only for old stakers who staked before the poolConsts.minCreateBond value was updated.
setAmountAsBN(staked);
setAmount(amountToHuman(staked, decimal));

return;
}

if (!partial.isZero()) {
setAmountAsBN(partial);
setAmount(amountToHuman(partial, decimal));
Expand Down
Loading