Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/MetaMask/metamask-mobile in…
Browse files Browse the repository at this point in the history
…to lightdark/actionbuttons
  • Loading branch information
brianacnguyen committed Sep 4, 2024
2 parents e499cac + eea663e commit 55c45f0
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 36 deletions.
2 changes: 1 addition & 1 deletion app/components/Views/confirmations/Send/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const initialState: DeepPartial<RootState> = {
transaction: {
value: '',
data: '0x0',
from: '0x1',
from: MOCK_ADDRESS_1,
gas: '',
gasPrice: '',
to: '0x2',
Expand Down
78 changes: 43 additions & 35 deletions app/components/Views/confirmations/SendFlow/Confirm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ import { TransactionConfirmViewSelectorsIDs } from '../../../../../../e2e/select
import { selectTransactionMetrics } from '../../../../../core/redux/slices/transactionMetrics';
import SimulationDetails from '../../../../UI/SimulationDetails/SimulationDetails';
import { selectUseTransactionSimulations } from '../../../../../selectors/preferencesController';
import {
generateInsufficientBalanceMessage,
validateBalance,
validateTokenTransaction,
} from './validation';
import { buildTransactionParams } from '../../../../../util/confirmation/transactions';

const EDIT = 'edit';
Expand Down Expand Up @@ -526,6 +531,21 @@ class Confirm extends PureComponent {
const contractBalanceChanged =
previousContractBalance !== newContractBalance;
const recipientIsDefined = transactionTo !== undefined;
const haveEIP1559TotalMaxHexChanged =
EIP1559GasTransaction.totalMaxHex !==
prevState.EIP1559GasTransaction.totalMaxHex;

const haveGasPropertiesChanged =
(this.props.gasFeeEstimates &&
gas &&
(!prevProps.gasFeeEstimates ||
!shallowEqual(
prevProps.gasFeeEstimates,
this.props.gasFeeEstimates,
) ||
gas !== prevProps?.transactionState?.transaction?.gas)) ||
haveEIP1559TotalMaxHexChanged;

if (
recipientIsDefined &&
(valueChanged || fromAddressChanged || contractBalanceChanged)
Expand All @@ -536,13 +556,7 @@ class Confirm extends PureComponent {
this.scrollView.scrollToEnd({ animated: true });
}

if (
this.props.gasFeeEstimates &&
gas &&
(!prevProps.gasFeeEstimates ||
!shallowEqual(prevProps.gasFeeEstimates, this.props.gasFeeEstimates) ||
gas !== prevProps?.transactionState?.transaction?.gas)
) {
if (haveGasPropertiesChanged) {
const gasEstimateTypeChanged =
prevProps.gasEstimateType !== this.props.gasEstimateType;
const gasSelected = gasEstimateTypeChanged
Expand Down Expand Up @@ -759,39 +773,33 @@ class Confirm extends PureComponent {
transaction: { value },
},
} = this.props;

const selectedAddress = transaction?.from;
let weiBalance, weiInput, error;

if (isDecimal(value)) {
if (selectedAsset.isETH || selectedAsset.tokenId) {
weiBalance = hexToBN(accounts[selectedAddress].balance);
const totalTransactionValue = hexToBN(total);
if (!weiBalance.gte(totalTransactionValue)) {
const amount = renderFromWei(totalTransactionValue.sub(weiBalance));
const tokenSymbol = getTicker(ticker);
error = strings('transaction.insufficient_amount', {
amount,
tokenSymbol,
});
}
} else {
const [, , amount] = decodeTransferData('transfer', transaction.data);
const weiBalance = hexToBN(accounts[selectedAddress].balance);
const totalTransactionValue = hexToBN(total);

weiBalance = hexToBN(contractBalances[selectedAsset.address]);
if (!isDecimal(value)) {
return strings('transaction.invalid_amount');
}

weiInput = hexToBN(amount);
error =
weiBalance && weiBalance.gte(weiInput)
? undefined
: strings('transaction.insufficient_tokens', {
token: selectedAsset.symbol,
});
if (selectedAsset.isETH || selectedAsset.tokenId) {
if (!validateBalance(weiBalance, totalTransactionValue)) {
return undefined;
}
} else {
error = strings('transaction.invalid_amount');
return generateInsufficientBalanceMessage(
weiBalance,
totalTransactionValue,
ticker,
);
}

return error;
return validateTokenTransaction(
transaction,
weiBalance,
totalTransactionValue,
contractBalances,
selectedAsset,
ticker,
);
};

setError = (errorMessage) => {
Expand Down
111 changes: 111 additions & 0 deletions app/components/Views/confirmations/SendFlow/Confirm/validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { BN } from 'ethereumjs-util';
import { validateTokenTransaction } from './validation';
import { renderFromWei, hexToBN } from '../../../../../util/number';
import {
getTicker,
decodeTransferData,
} from '../../../../../util/transactions';
import { strings } from '../../../../../../locales/i18n';

jest.mock('../../../../../util/number', () => ({
renderFromWei: jest.fn(),
hexToBN: jest.fn(),
}));

jest.mock('../../../../../util/transactions', () => ({
getTicker: jest.fn(),
decodeTransferData: jest.fn(),
}));

jest.mock('../../../../../../locales/i18n', () => ({
strings: jest.fn(),
}));

describe('validateTokenTransaction', () => {
it('return an error message if weiBalance is less than totalTransactionValue', () => {
const transaction = { data: '0x' };
const weiBalance = new BN('1000');
const totalTransactionValue = new BN('2000');
const contractBalances = { '0x123': '1000' };
const selectedAsset = { address: '0x123', decimals: '18', symbol: 'TOKEN' };
const ticker = 'TOKEN';

(renderFromWei as jest.Mock).mockReturnValue('1');
(getTicker as jest.Mock).mockReturnValue('TOKEN');
(strings as jest.Mock).mockReturnValue('Insufficient amount');

const result = validateTokenTransaction(
transaction,
weiBalance,
totalTransactionValue,
contractBalances,
selectedAsset,
ticker,
);

expect(result).toBe('Insufficient amount');
expect(renderFromWei).toHaveBeenCalledWith(
totalTransactionValue.sub(weiBalance),
);
expect(getTicker).toHaveBeenCalledWith(ticker);
expect(strings).toHaveBeenCalledWith('transaction.insufficient_amount', {
amount: '1',
tokenSymbol: 'TOKEN',
});
});

it('return an error message if tokenBalance is less than weiInput', () => {
const transaction = { data: '0x' };
const weiBalance = new BN('2000');
const totalTransactionValue = new BN('1000');
const contractBalances = { '0x123': '1000' };
const selectedAsset = { address: '0x123', decimals: '18', symbol: 'TOKEN' };
const ticker = 'TOKEN';

(decodeTransferData as jest.Mock).mockReturnValue([null, null, '5000']);
(hexToBN as jest.Mock).mockImplementation((value) => new BN(value));
(strings as jest.Mock).mockReturnValue('Insufficient tokens');

const result = validateTokenTransaction(
transaction,
weiBalance,
totalTransactionValue,
contractBalances,
selectedAsset,
ticker,
);

expect(result).toBe('Insufficient tokens');
expect(decodeTransferData).toHaveBeenCalledWith(
'transfer',
transaction.data,
);
expect(hexToBN).toHaveBeenCalledWith('5000');
expect(strings).toHaveBeenCalledWith('transaction.insufficient_tokens', {
token: 'TOKEN',
});
});

it('return undefined if weiBalance and tokenBalance are sufficient', () => {
const transaction = { data: '0x' };
const weiBalance = new BN('2000');
const totalTransactionValue = new BN('1000');
const contractBalances = { '0x123': '5000' };
const selectedAsset = { address: '0x123', decimals: '18', symbol: 'TOKEN' };
const ticker = 'TOKEN';

(decodeTransferData as jest.Mock).mockReturnValue([null, null, '1000']);
(hexToBN as jest.Mock).mockImplementation((value) => new BN(value));

const result = validateTokenTransaction(
transaction,
weiBalance,
totalTransactionValue,
contractBalances,
selectedAsset,
ticker,
);

expect(result).toBeUndefined();
});
});
60 changes: 60 additions & 0 deletions app/components/Views/confirmations/SendFlow/Confirm/validation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { renderFromWei, hexToBN } from '../../../../../util/number';
import {
getTicker,
decodeTransferData,
} from '../../../../../util/transactions';
import { strings } from '../../../../../../locales/i18n';
import { BN } from 'ethereumjs-util';

interface SelectedAsset {
address: string;
decimals: string;
symbol: string;
}

export const generateInsufficientBalanceMessage = (
weiBalance: BN,
transactionValue: BN,
ticker: string,
) => {
const amount = renderFromWei(transactionValue.sub(weiBalance));
const tokenSymbol = getTicker(ticker);
return strings('transaction.insufficient_amount', {
amount,
tokenSymbol,
});
};

export const validateBalance = (weiBalance: BN, transactionValue: BN) =>
!weiBalance.gte(transactionValue);

export const validateTokenTransaction = (
transaction: {
data: string;
},
weiBalance: BN,
totalTransactionValue: BN,
contractBalances: { [key: string]: string },
selectedAsset: SelectedAsset,
ticker: string,
) => {
if (validateBalance(weiBalance, totalTransactionValue)) {
return generateInsufficientBalanceMessage(
weiBalance,
totalTransactionValue,
ticker,
);
}

const [, , amount] = decodeTransferData('transfer', transaction.data);
const tokenBalance = hexToBN(contractBalances[selectedAsset.address]);
const weiInput = hexToBN(amount);

if (!tokenBalance.gte(weiInput)) {
return strings('transaction.insufficient_tokens', {
token: selectedAsset.symbol,
});
}

return undefined;
};

0 comments on commit 55c45f0

Please sign in to comment.