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

[Issue - 3713] Extension - Fix bug validating recipient balance when sending Substrate token #3771

Open
wants to merge 11 commits into
base: subwallet-dev
Choose a base branch
from
5 changes: 5 additions & 0 deletions packages/extension-base/src/background/KoniTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,13 +232,13 @@
externalUrl?: string;
rarity?: string;
description?: string;
properties?: Record<any, any> | null;

Check warning on line 235 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type

Check warning on line 235 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
}

interface NftItemExtraInfo {
type?: _AssetType.ERC721 | _AssetType.PSP34 | RMRK_VER; // for sending
rmrk_ver?: RMRK_VER;
onChainOption?: any; // for sending PSP-34 tokens, should be done better

Check warning on line 241 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
assetHubType?: AssetHubNftType // for sending assetHub nft. There're 2 types nft
}

Expand Down Expand Up @@ -543,7 +543,7 @@
[ExtrinsicType.STAKING_COMPOUNDING]: RequestTuringStakeCompound,
[ExtrinsicType.STAKING_CANCEL_COMPOUNDING]: RequestTuringCancelStakeCompound,
[ExtrinsicType.STAKING_CANCEL_UNSTAKE]: RequestStakeCancelWithdrawal,
[ExtrinsicType.STAKING_POOL_WITHDRAW]: any,

Check warning on line 546 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type

// Yield
[ExtrinsicType.JOIN_YIELD_POOL]: RequestYieldStepSubmit,
Expand Down Expand Up @@ -574,9 +574,9 @@
[ExtrinsicType.CLAIM_BRIDGE]: RequestClaimBridge

[ExtrinsicType.EVM_EXECUTE]: TransactionConfig,
[ExtrinsicType.CROWDLOAN]: any,

Check warning on line 577 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
[ExtrinsicType.SWAP]: SwapTxData
[ExtrinsicType.UNKNOWN]: any

Check warning on line 579 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
}

export enum ExtrinsicStatus {
Expand Down Expand Up @@ -616,6 +616,11 @@
symbol: string;
}

export interface SufficientMetadata {
isSufficient: boolean,
minBalance: number
}

export interface AmountData extends BasicTokenInfo {
value: string;
metadata?: unknown;
Expand Down Expand Up @@ -670,7 +675,7 @@
// : T extends ExtrinsicType.MINT_VDOT
// ? Pick<SubmitBifrostLiquidStaking, 'rewardTokenSlug' | 'estimatedAmountReceived'>
// : undefined;
export interface TransactionHistoryItem<ET extends ExtrinsicType = ExtrinsicType.TRANSFER_BALANCE> {

Check warning on line 678 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

'ET' is defined but never used
origin?: 'app' | 'migration' | 'subsquid' | 'subscan', // 'app' or history source
callhash?: string,
signature?: string,
Expand All @@ -695,7 +700,7 @@
tip?: AmountData,
fee?: AmountData,
explorerUrl?: string,
additionalInfo?: any,

Check warning on line 703 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
startBlock?: number,
nonce?: number,
addressPrefix?: number
Expand Down Expand Up @@ -926,12 +931,12 @@
recipientAddress: string,

nftItemName?: string, // Use for confirmation view only
params: Record<string, any>,

Check warning on line 934 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
nftItem: NftItem
}

export interface EvmNftTransaction extends ValidateTransactionResponse {
tx: Record<string, any> | null;

Check warning on line 939 in packages/extension-base/src/background/KoniTypes.ts

View workflow job for this annotation

GitHub Actions / Build Development Preview

Unexpected any. Specify a different type
}

export interface ValidateNetworkResponse {
Expand Down
66 changes: 48 additions & 18 deletions packages/extension-base/src/core/logic-validation/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { TransactionError } from '@subwallet/extension-base/background/errors/Tr
import { _Address, AmountData, ExtrinsicDataTypeMap, ExtrinsicType, FeeData } from '@subwallet/extension-base/background/KoniTypes';
import { TransactionWarning } from '@subwallet/extension-base/background/warnings/TransactionWarning';
import { LEDGER_SIGNING_COMPATIBLE_MAP, SIGNING_COMPATIBLE_MAP, XCM_MIN_AMOUNT_RATIO } from '@subwallet/extension-base/constants';
import { _canAccountBeReaped } from '@subwallet/extension-base/core/substrate/system-pallet';
import { _canAccountBeReaped, _isAccountActive } from '@subwallet/extension-base/core/substrate/system-pallet';
import { FrameSystemAccountInfo } from '@subwallet/extension-base/core/substrate/types';
import { isBounceableAddress } from '@subwallet/extension-base/services/balance-service/helpers/subscribe/ton/utils';
import { _TRANSFER_CHAIN_GROUP } from '@subwallet/extension-base/services/chain-service/constants';
import { _EvmApi, _TonApi } from '@subwallet/extension-base/services/chain-service/types';
import { _getChainExistentialDeposit, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getTokenMinAmount, _isNativeToken, _isTokenEvmSmartContract, _isTokenTonSmartContract } from '@subwallet/extension-base/services/chain-service/utils';
import { _getAssetDecimals, _getChainExistentialDeposit, _getChainNativeTokenBasicInfo, _getContractAddressOfToken, _getTokenMinAmount, _isNativeToken, _isTokenEvmSmartContract, _isTokenTonSmartContract } from '@subwallet/extension-base/services/chain-service/utils';
import { calculateGasFeeParams } from '@subwallet/extension-base/services/fee-service/utils';
import { isSubstrateTransaction, isTonTransaction } from '@subwallet/extension-base/services/transaction-service/helpers';
import { OptionalSWTransaction, SWTransactionInput, SWTransactionResponse } from '@subwallet/extension-base/services/transaction-service/types';
Expand Down Expand Up @@ -56,37 +56,67 @@ export function validateTransferRequest (tokenInfo: _ChainAsset, from: _Address,
return [errors, keypair, transferValue];
}

export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenFreeBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, receiverNativeTransferable?: string): [TransactionWarning[], TransactionError[]] {
const minAmount = _getTokenMinAmount(tokenInfo);
const nativeMinAmount = _getTokenMinAmount(nativeTokenInfo);
export function additionalValidateTransferForRecipient (
sendingTokenInfo: _ChainAsset,
nativeTokenInfo: _ChainAsset,
extrinsicType: ExtrinsicType,
receiverSendingTokenKeepAliveBalance: bigint,
transferAmount: bigint,
senderSendingTokenTransferable?: bigint,
receiverSystemAccountInfo?: FrameSystemAccountInfo,
isSendingTokenSufficient?: boolean
): [TransactionWarning[], TransactionError[]] {
const sendingTokenMinAmount = BigInt(_getTokenMinAmount(sendingTokenInfo));
const nativeTokenMinAmount = _getTokenMinAmount(nativeTokenInfo);

const warnings: TransactionWarning[] = [];
const errors: TransactionError[] = [];

// Check ed of not native token for sender after sending
if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && senderTransferTokenTransferable) {
if (new BigN(senderTransferTokenTransferable).minus(transferAmount).lt(minAmount)) {
const remainingSendingTokenOfSenderEnoughED = senderSendingTokenTransferable ? senderSendingTokenTransferable - transferAmount >= sendingTokenMinAmount : false;
const isReceiverAliveByNativeToken = _isAccountActive(receiverSystemAccountInfo as FrameSystemAccountInfo);
const isReceivingAmountPassED = receiverSendingTokenKeepAliveBalance + transferAmount >= sendingTokenMinAmount;

if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN) {
if (!remainingSendingTokenOfSenderEnoughED) {
const warning = new TransactionWarning(BasicTxWarningCode.NOT_ENOUGH_EXISTENTIAL_DEPOSIT);

warnings.push(warning);
}
}

// Check ed for receiver before sending
if (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && receiverNativeTransferable) {
if (new BigN(receiverNativeTransferable).lt(nativeMinAmount)) {
const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('The recipient account has {{amount}} {{nativeSymbol}} which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: receiverNativeTransferable, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } }));
if (!isReceiverAliveByNativeToken && !isSendingTokenSufficient) {
const balanceKeepAlive = formatNumber(nativeTokenMinAmount, _getAssetDecimals(nativeTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(nativeTokenInfo) || 6 });

const error = new TransactionError(
TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT,
t('The recipient account has less than {{amount}} {{nativeSymbol}}, which can lead to your {{localSymbol}} being lost. Change recipient account and try again', { replace: { amount: balanceKeepAlive, nativeSymbol: nativeTokenInfo.symbol, localSymbol: sendingTokenInfo.symbol } })
);

errors.push(error);
}

if (!isReceivingAmountPassED) {
const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance;

const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 });

const error = new TransactionError(
TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT,
t('You must transfer at least {{amount}} {{symbol}} to avoid losing funds on the recipient account. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })
);

errors.push(error);
}
}

// Check ed for receiver after sending
if (new BigN(receiverTransferTokenFreeBalance).plus(transferAmount).lt(minAmount)) {
const atLeast = new BigN(minAmount).minus(receiverTransferTokenFreeBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1);
if (!isReceivingAmountPassED) {
const atLeast = sendingTokenMinAmount - receiverSendingTokenKeepAliveBalance;

const atLeastStr = formatNumber(atLeast, tokenInfo.decimals || 0, balanceFormatter, { maxNumberFormat: tokenInfo.decimals || 6 });
const atLeastStr = formatNumber(atLeast.toString(), _getAssetDecimals(sendingTokenInfo), balanceFormatter, { maxNumberFormat: _getAssetDecimals(sendingTokenInfo) || 6 });

const error = new TransactionError(TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT, t('You must transfer at least {{amount}} {{symbol}} to keep the destination account alive', { replace: { amount: atLeastStr, symbol: tokenInfo.symbol } }));
const error = new TransactionError(
TransferTxErrorType.RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT,
t('You must transfer at least {{amount}} {{symbol}} to keep the recipient account alive. Increase amount and try again', { replace: { amount: atLeastStr, symbol: sendingTokenInfo.symbol } })
);

errors.push(error);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function _canAccountBeReaped (accountInfo: FrameSystemAccountInfo): boole
}

export function _isAccountActive (accountInfo: FrameSystemAccountInfo): boolean {
return accountInfo.providers === 0 && accountInfo.consumers === 0;
return !(accountInfo.consumers === 0 && accountInfo.providers === 0 && accountInfo.sufficients === 0);
}

export function _getSystemPalletTotalBalance (accountInfo: FrameSystemAccountInfo): bigint {
Expand Down
Loading
Loading