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 1 commit into
base: subwallet-dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 14 additions & 7 deletions packages/extension-base/src/core/logic-validation/transfer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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';
Expand Down Expand Up @@ -56,7 +56,7 @@ 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[]] {
export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenInfo: _ChainAsset, extrinsicType: ExtrinsicType, receiverTransferTokenTotalBalance: string, transferAmount: string, senderTransferTokenTransferable?: string, _receiverNativeTotal?: string, isReceiverActive?: unknown): [TransactionWarning[], TransactionError[]] {
const minAmount = _getTokenMinAmount(tokenInfo);
const nativeMinAmount = _getTokenMinAmount(nativeTokenInfo);
const warnings: TransactionWarning[] = [];
Expand All @@ -72,17 +72,24 @@ export function additionalValidateTransfer (tokenInfo: _ChainAsset, nativeTokenI
}

// 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 (extrinsicType === ExtrinsicType.TRANSFER_TOKEN && _receiverNativeTotal) {
if (new BigN(_receiverNativeTotal).lt(nativeMinAmount) && new BigN(nativeMinAmount).gt(0)) {
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: _receiverNativeTotal, nativeSymbol: nativeTokenInfo.symbol, localSymbol: tokenInfo.symbol } }));

errors.push(error);
}
}

// Check if receiver's account is active
if (isReceiverActive && _isAccountActive(isReceiverActive as FrameSystemAccountInfo)) {
const error = new TransactionError(TransferTxErrorType.RECEIVER_ACCOUNT_INACTIVE, t('The recipient account may be inactive. Change recipient account and try again'));

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 (new BigN(receiverTransferTokenTotalBalance).plus(transferAmount).lt(minAmount)) {
const atLeast = new BigN(minAmount).minus(receiverTransferTokenTotalBalance).plus((tokenInfo.decimals || 0) === 0 ? 0 : 1);

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

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
18 changes: 12 additions & 6 deletions packages/extension-base/src/koni/background/handlers/Extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1376,22 +1376,24 @@ export default class KoniExtension {

const additionalValidator = async (inputTransaction: SWTransactionResponse): Promise<void> => {
let senderTransferTokenTransferable: string | undefined;
let receiverNativeTransferable: string | undefined;
let receiverNativeTotal: string | undefined;
let isReceiverActive: unknown;

// Check ed for sender
if (!isTransferNativeToken) {
const [_senderTransferTokenTransferable, _receiverNativeTransferable] = await Promise.all([
const [_senderTransferTokenTransferable, _receiverNativeTotal] = await Promise.all([
this.getAddressTransferableBalance({ address: from, networkKey, token: tokenSlug, extrinsicType }),
this.getAddressTransferableBalance({ address: to, networkKey, token: nativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE })
this.getAddressTotalBalance({ address: to, networkKey, token: nativeTokenSlug, extrinsicType: ExtrinsicType.TRANSFER_BALANCE })
]);

senderTransferTokenTransferable = _senderTransferTokenTransferable.value;
receiverNativeTransferable = _receiverNativeTransferable.value;
receiverNativeTotal = _receiverNativeTotal.value;
isReceiverActive = _receiverNativeTotal.metadata;
}

const { value: receiverTransferTokenTransferable } = await this.getAddressTransferableBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts
const { value: receiverTransferTokenTransferable } = await this.getAddressTotalBalance({ address: to, networkKey, token: tokenSlug, extrinsicType }); // todo: shouldn't be just transferable, locked also counts

const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTransferable, transferAmount.value, senderTransferTokenTransferable, receiverNativeTransferable);
const [warnings, errors] = additionalValidateTransfer(transferTokenInfo, nativeTokenInfo, extrinsicType, receiverTransferTokenTransferable, transferAmount.value, senderTransferTokenTransferable, receiverNativeTotal, isReceiverActive);

warnings.length && inputTransaction.warnings.push(...warnings);
errors.length && inputTransaction.errors.push(...errors);
Expand Down Expand Up @@ -1652,6 +1654,10 @@ export default class KoniExtension {
return await this.#koniState.balanceService.getTransferableBalance(address, networkKey, token, extrinsicType);
}

private async getAddressTotalBalance ({ address, extrinsicType, networkKey, token }: RequestFreeBalance): Promise<AmountData> {

Choose a reason for hiding this comment

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

Recheck with _MANTA_ZK_CHAIN_GROUP case

return await this.#koniState.balanceService.getTotalBalance(address, networkKey, token, extrinsicType);
}

private async getMaxTransferable ({ address, destChain, isXcmTransfer, networkKey, token }: RequestMaxTransferable): Promise<AmountData> {
const tokenInfo = token ? this.#koniState.chainService.getAssetBySlug(token) : this.#koniState.chainService.getNativeTokenInfo(networkKey);

Expand Down
23 changes: 21 additions & 2 deletions packages/extension-base/src/services/balance-service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { addLazy, createPromiseHandler, isAccountAll, PromiseHandler, waitTimeou
import { getKeypairTypeByAddress } from '@subwallet/keyring';
import { EthereumKeypairTypes, SubstrateKeypairTypes } from '@subwallet/keyring/types';
import keyring from '@subwallet/ui-keyring';
import BigN from 'bignumber.js';
import { t } from 'i18next';
import { BehaviorSubject } from 'rxjs';

Expand Down Expand Up @@ -189,7 +190,7 @@ export class BalanceService implements StoppableServiceInterface {
}

/** Subscribe token free balance of an address on chain */

Choose a reason for hiding this comment

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

Update

public async subscribeTransferableBalance (address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> {
public async subscribeBalance (address: string, chain: string, tokenSlug: string | undefined, balanceType: 'transferable' | 'total', extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> {

Choose a reason for hiding this comment

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

Should better create BalanceType Enum

const chainInfo = this.state.chainService.getChainInfoByKey(chain);
const chainState = this.state.chainService.getChainStateByKey(chain);

Expand Down Expand Up @@ -218,10 +219,14 @@ export class BalanceService implements StoppableServiceInterface {
unsub = subscribeBalance([address], [chain], [tSlug], assetMap, chainInfoMap, substrateApiMap, evmApiMap, tonApiMap, (result) => {
const rs = result[0];

const value = balanceType === 'total'
? new BigN(rs.free).plus(new BigN(rs.locked)).toString()
: rs.free;

if (rs.tokenSlug === tSlug) {
hasError = false;
const balance: AmountData = {
value: rs.free,
value,
decimals: tokenInfo.decimals || 0,
symbol: tokenInfo.symbol,
metadata: rs.metadata
Expand All @@ -247,6 +252,14 @@ export class BalanceService implements StoppableServiceInterface {
});
}

public async subscribeTransferableBalance (address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> {
return this.subscribeBalance(address, chain, tokenSlug, 'transferable', extrinsicType, callback);
}

public async subscribeTotalBalance (address: string, chain: string, tokenSlug: string | undefined, extrinsicType?: ExtrinsicType, callback?: (rs: AmountData) => void): Promise<[() => void, AmountData]> {
return this.subscribeBalance(address, chain, tokenSlug, 'total', extrinsicType, callback);
}

/**
* @public
* @async
Expand All @@ -264,6 +277,12 @@ export class BalanceService implements StoppableServiceInterface {
return balance;
}

public async getTotalBalance (address: string, chain: string, tokenSlug?: string, extrinsicType?: ExtrinsicType): Promise<AmountData> {
const [, balance] = await this.subscribeTotalBalance(address, chain, tokenSlug, extrinsicType);

return balance;
}

/** Remove balance from the subject object by addresses */
public removeBalanceByAddresses (addresses: string[]) {
this.balanceMap.removeBalanceItems([...addresses, ALL_ACCOUNT_KEY]);
Expand Down
1 change: 1 addition & 0 deletions packages/extension-base/src/types/transaction/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export enum TransferTxErrorType {
INVALID_TOKEN = 'INVALID_TOKEN',
TRANSFER_ERROR = 'TRANSFER_ERROR',
RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT = 'RECEIVER_NOT_ENOUGH_EXISTENTIAL_DEPOSIT',
RECEIVER_ACCOUNT_INACTIVE = 'RECEIVER_ACCOUNT_INACTIVE'
}

export type TransactionErrorType = BasicTxErrorType | TransferTxErrorType | StakingTxErrorType | YieldValidationStatus | SwapErrorType;
Loading