1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Fix contract interaction recipient and contract address (#18855)

This commit is contained in:
Ariella Vu 2023-05-18 18:19:40 +01:00 committed by GitHub
parent 52109a1829
commit 813ad984ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 227 additions and 51 deletions

View File

@ -14,6 +14,16 @@ export function isBurnAddress(address: string) {
return address === BURN_ADDRESS; return address === BURN_ADDRESS;
} }
export function isEmptyHexString(value: string): boolean {
return [
undefined,
null,
'0x',
'0x0',
'0x0000000000000000000000000000000000000000000000000000000000000000',
].includes(value);
}
/** /**
* Validates that the input is a hex address. This utility method is a thin * Validates that the input is a hex address. This utility method is a thin
* wrapper around ethereumjs-util.isValidAddress, with the exception that it * wrapper around ethereumjs-util.isValidAddress, with the exception that it

View File

@ -90,7 +90,7 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
style="height: 24px; width: 24px; border-radius: 12px;" style="height: 24px; width: 24px; border-radius: 12px;"
> >
<div <div
style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 24px; height: 24px; display: inline-block; background: rgb(24, 151, 242);" style="border-radius: 50px; overflow: hidden; padding: 0px; margin: 0px; width: 24px; height: 24px; display: inline-block; background: rgb(245, 228, 0);"
> >
<svg <svg
height="24" height="24"
@ -99,25 +99,25 @@ exports[`Confirm Transaction Base should match snapshot 1`] = `
y="0" y="0"
> >
<rect <rect
fill="#2362E1" fill="#F2BE02"
height="24" height="24"
transform="translate(2.6919976385943882 -4.000732967425142) rotate(458.4 12 12)" transform="translate(4.11204385070503 -3.89652025148055) rotate(387.1 12 12)"
width="24" width="24"
x="0" x="0"
y="0" y="0"
/> />
<rect <rect
fill="#F94301" fill="#01778E"
height="24" height="24"
transform="translate(-11.522594467237658 5.994263726614974) rotate(268.8 12 12)" transform="translate(-1.9540919563657109 12.528454691642345) rotate(227.4 12 12)"
width="24" width="24"
x="0" x="0"
y="0" y="0"
/> />
<rect <rect
fill="#FA7900" fill="#C81435"
height="24" height="24"
transform="translate(-6.8071905594760675 22.110011910512533) rotate(117.3 12 12)" transform="translate(13.477268130678231 -9.444486788922836) rotate(422.3 12 12)"
width="24" width="24"
x="0" x="0"
y="0" y="0"

View File

@ -431,7 +431,10 @@ export default class ConfirmTransactionBase extends Component {
) : null; ) : null;
const simulationFailureWarning = () => ( const simulationFailureWarning = () => (
<div className="confirm-page-container-content__error-container"> <div
className="confirm-page-container-content__error-container"
key="confirm-transaction-base_simulation-error-container"
>
<SimulationErrorMessage <SimulationErrorMessage
userAcknowledgedGasMissing={userAcknowledgedGasMissing} userAcknowledgedGasMissing={userAcknowledgedGasMissing}
setUserAcknowledgedGasMissing={() => setUserAcknowledgedGasMissing={() =>
@ -463,6 +466,7 @@ export default class ConfirmTransactionBase extends Component {
renderSimulationFailureWarning && simulationFailureWarning(), renderSimulationFailureWarning && simulationFailureWarning(),
!renderSimulationFailureWarning && ( !renderSimulationFailureWarning && (
<ConfirmGasDisplay <ConfirmGasDisplay
key="confirm-transaction-base_confirm-gas-display"
userAcknowledgedGasMissing={userAcknowledgedGasMissing} userAcknowledgedGasMissing={userAcknowledgedGasMissing}
/> />
), ),

View File

@ -55,7 +55,10 @@ import {
transactionMatchesNetwork, transactionMatchesNetwork,
txParamsAreDappSuggested, txParamsAreDappSuggested,
} from '../../../shared/modules/transaction.utils'; } from '../../../shared/modules/transaction.utils';
import { toChecksumHexAddress } from '../../../shared/modules/hexstring-utils'; import {
isEmptyHexString,
toChecksumHexAddress,
} from '../../../shared/modules/hexstring-utils';
import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app'; import { getGasLoadingAnimationIsShowing } from '../../ducks/app/app';
import { isLegacyTransaction } from '../../helpers/utils/transactions.util'; import { isLegacyTransaction } from '../../helpers/utils/transactions.util';
@ -129,10 +132,13 @@ const mapStateToProps = (state, ownProps) => {
const { balance } = accounts[fromAddress]; const { balance } = accounts[fromAddress];
const { name: fromName } = identities[fromAddress]; const { name: fromName } = identities[fromAddress];
let toAddress = txParamsToAddress;
if (type !== TransactionType.simpleSend) { const isSendingAmount =
toAddress = propsToAddress || tokenToAddress || txParamsToAddress; type === TransactionType.simpleSend || !isEmptyHexString(amount);
}
const toAddress = isSendingAmount
? txParamsToAddress
: propsToAddress || tokenToAddress || txParamsToAddress;
const toAccounts = getSendToAccounts(state); const toAccounts = getSendToAccounts(state);

View File

@ -8,6 +8,10 @@ import { INITIAL_SEND_STATE_FOR_EXISTING_DRAFT } from '../../../test/jest/mocks'
import { GasEstimateTypes } from '../../../shared/constants/gas'; import { GasEstimateTypes } from '../../../shared/constants/gas';
import { KeyringType } from '../../../shared/constants/keyring'; import { KeyringType } from '../../../shared/constants/keyring';
import { CHAIN_IDS } from '../../../shared/constants/network'; import { CHAIN_IDS } from '../../../shared/constants/network';
import {
TransactionStatus,
TransactionType,
} from '../../../shared/constants/transaction';
import { domainInitialState } from '../../ducks/domains'; import { domainInitialState } from '../../ducks/domains';
import ConfirmTransactionBase from './confirm-transaction-base.container'; import ConfirmTransactionBase from './confirm-transaction-base.container';
@ -22,6 +26,31 @@ setBackgroundConnection({
getNextNonce: jest.fn(), getNextNonce: jest.fn(),
}); });
const mockNetworkId = '5';
const mockTxParamsFromAddress = '0x123456789';
const mockTxParamsToAddress = '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e';
const mockTxParamsToAddressConcat = '0x85c...D65e';
const mockParsedTxDataToAddressWithout0x =
'e57e7847fd3661a9b7c86aaf1daea08d9da5750a';
const mockParsedTxDataToAddress = '0xe57...750A';
const mockPropsToAddress = '0x33m1685cfceaa5c0bdb1609fc536e9a8387dd567';
const mockPropsToAddressConcat = '0x33m...d567';
const mockTxParams = {
from: mockTxParamsFromAddress,
to: mockTxParamsToAddress,
value: '0x5af3107a4000',
gas: '0x5208',
maxFeePerGas: '0x59682f16',
maxPriorityFeePerGas: '0x59682f00',
type: '0x2',
data: `0xa22cb465000000000000000000000000${mockParsedTxDataToAddressWithout0x}0000000000000000000000000000000000000000000000000000000000000001`,
};
const baseStore = { const baseStore = {
send: { send: {
...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT, ...INITIAL_SEND_STATE_FOR_EXISTING_DRAFT,
@ -37,17 +66,8 @@ const baseStore = {
unapprovedTxs: { unapprovedTxs: {
1: { 1: {
id: 1, id: 1,
metamaskNetworkId: '5', metamaskNetworkId: mockNetworkId,
txParams: { txParams: { ...mockTxParams },
from: '0x0',
to: '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e',
value: '0x5af3107a4000',
gas: '0x5208',
maxFeePerGas: '0x59682f16',
maxPriorityFeePerGas: '0x59682f00',
type: '0x2',
data: 'data',
},
}, },
}, },
gasEstimateType: GasEstimateTypes.legacy, gasEstimateType: GasEstimateTypes.legacy,
@ -56,14 +76,14 @@ const baseStore = {
medium: '1', medium: '1',
fast: '2', fast: '2',
}, },
selectedAddress: '0x0', selectedAddress: mockTxParamsFromAddress,
keyrings: [ keyrings: [
{ {
type: KeyringType.hdKeyTree, type: KeyringType.hdKeyTree,
accounts: ['0x0'], accounts: ['0x0'],
}, },
], ],
networkId: '5', networkId: mockNetworkId,
networkDetails: { networkDetails: {
EIPS: {}, EIPS: {},
}, },
@ -86,9 +106,17 @@ const baseStore = {
[CHAIN_IDS.GOERLI]: {}, [CHAIN_IDS.GOERLI]: {},
}, },
accounts: { accounts: {
'0x0': { balance: '0x0', address: '0x0' }, [mockTxParamsFromAddress]: {
balance: '0x0',
address: mockTxParamsFromAddress,
},
},
identities: {
[mockTxParamsFromAddress]: { address: mockTxParamsFromAddress },
[mockTxParamsToAddress]: {
name: 'Test Address 1',
},
}, },
identities: { '0x0': { address: '0x0' } },
tokenAddress: '0x32e6c34cd57087abbd59b5a4aecc4cb495924356', tokenAddress: '0x32e6c34cd57087abbd59b5a4aecc4cb495924356',
tokenList: {}, tokenList: {},
ensResolutionsByAddress: {}, ensResolutionsByAddress: {},
@ -97,24 +125,16 @@ const baseStore = {
confirmTransaction: { confirmTransaction: {
txData: { txData: {
id: 1, id: 1,
metamaskNetworkId: mockNetworkId,
txParams: { ...mockTxParams },
time: 1675012496170, time: 1675012496170,
status: 'unapproved', status: TransactionStatus.unapproved,
metamaskNetworkId: '5',
originalGasEstimate: '0x5208', originalGasEstimate: '0x5208',
userEditedGasLimit: false, userEditedGasLimit: false,
chainId: '0x5', chainId: '0x5',
loadingDefaults: false, loadingDefaults: false,
dappSuggestedGasFees: null, dappSuggestedGasFees: null,
sendFlowHistory: [], sendFlowHistory: [],
txParams: {
from: '0x0',
to: '0x85c1685cfceaa5c0bdb1609fc536e9a8387dd65e',
value: '0x5af3107a4000',
gas: '0x5208',
maxFeePerGas: '0x59682f16',
maxPriorityFeePerGas: '0x59682f00',
type: '0x2',
},
origin: 'metamask', origin: 'metamask',
actionId: 1675012496153.2039, actionId: 1675012496153.2039,
type: 'simpleSend', type: 'simpleSend',
@ -145,6 +165,16 @@ const baseStore = {
}, },
}; };
const mockedStore = jest.mocked(baseStore);
const mockedStoreWithConfirmTxParams = (_mockTxParams = mockTxParams) => {
mockedStore.metamask.unapprovedTxs[1].txParams = { ..._mockTxParams };
mockedStore.confirmTransaction.txData.txParams = { ..._mockTxParams };
};
const sendToRecipientSelector =
'.sender-to-recipient__party--recipient .sender-to-recipient__name';
describe('Confirm Transaction Base', () => { describe('Confirm Transaction Base', () => {
it('should match snapshot', () => { it('should match snapshot', () => {
const store = configureMockStore(middleware)(baseStore); const store = configureMockStore(middleware)(baseStore);
@ -155,29 +185,39 @@ describe('Confirm Transaction Base', () => {
expect(container).toMatchSnapshot(); expect(container).toMatchSnapshot();
}); });
it('should contain L1 L2 fee details for optimism', () => { it('should not contain L1 L2 fee details for chains that are not optimism', () => {
baseStore.metamask.providerConfig.chainId = CHAIN_IDS.OPTIMISM;
baseStore.confirmTransaction.txData.chainId = CHAIN_IDS.OPTIMISM;
const store = configureMockStore(middleware)(baseStore); const store = configureMockStore(middleware)(baseStore);
const { getByText } = renderWithProvider( const { queryByText } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />, <ConfirmTransactionBase actionKey="confirm" />,
store, store,
); );
expect(getByText('Layer 1 fees')).toBeInTheDocument(); expect(queryByText('Layer 1 fees')).not.toBeInTheDocument();
expect(getByText('Layer 2 gas fee')).toBeInTheDocument(); expect(queryByText('Layer 2 gas fee')).not.toBeInTheDocument();
});
it('should contain L1 L2 fee details for optimism', () => {
mockedStore.metamask.providerConfig.chainId = CHAIN_IDS.OPTIMISM;
mockedStore.confirmTransaction.txData.chainId = CHAIN_IDS.OPTIMISM;
const store = configureMockStore(middleware)(mockedStore);
const { queryByText } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />,
store,
);
expect(queryByText('Layer 1 fees')).toBeInTheDocument();
expect(queryByText('Layer 2 gas fee')).toBeInTheDocument();
}); });
it('should render NoteToTrader when isNoteToTraderSupported is true', () => { it('should render NoteToTrader when isNoteToTraderSupported is true', () => {
baseStore.metamask.custodyAccountDetails = { mockedStore.metamask.custodyAccountDetails = {
'0x0': { [mockTxParamsFromAddress]: {
address: '0x0', address: mockTxParamsFromAddress,
details: 'details', details: 'details',
custodyType: 'testCustody - Saturn', custodyType: 'testCustody - Saturn',
custodianName: 'saturn-dev', custodianName: 'saturn-dev',
}, },
}; };
baseStore.metamask.mmiConfiguration = { mockedStore.metamask.mmiConfiguration = {
custodians: [ custodians: [
{ {
name: 'saturn-dev', name: 'saturn-dev',
@ -187,11 +227,127 @@ describe('Confirm Transaction Base', () => {
], ],
}; };
const store = configureMockStore(middleware)(baseStore); const store = configureMockStore(middleware)(mockedStore);
const { getByTestId } = renderWithProvider( const { getByTestId } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />, <ConfirmTransactionBase actionKey="confirm" />,
store, store,
); );
expect(getByTestId('transaction-note')).toBeInTheDocument(); expect(getByTestId('transaction-note')).toBeInTheDocument();
}); });
describe('when rendering the recipient value', () => {
describe(`when the transaction is a ${TransactionType.simpleSend} type`, () => {
it(`should use txParams.to address`, () => {
const store = configureMockStore(middleware)(mockedStore);
const { container } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />,
store,
);
const recipientElem = container.querySelector(sendToRecipientSelector);
expect(recipientElem).toHaveTextContent(mockTxParamsToAddressConcat);
});
it(`should use txParams.to address even if there is no amount sent`, () => {
mockedStoreWithConfirmTxParams({
...mockTxParams,
value: '0x0',
});
const store = configureMockStore(middleware)(mockedStore);
const { container } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />,
store,
);
const recipientElem = container.querySelector(sendToRecipientSelector);
expect(recipientElem).toHaveTextContent(mockTxParamsToAddressConcat);
});
});
describe(`when the transaction is NOT a ${TransactionType.simpleSend} type`, () => {
beforeEach(() => {
mockedStore.confirmTransaction.txData.type =
TransactionType.contractInteraction;
});
describe('when there is an amount being sent (it should be treated as a general contract intereaction rather than custom one)', () => {
it('should use txParams.to address (contract address)', () => {
mockedStoreWithConfirmTxParams({
...mockTxParams,
value: '0x45666',
});
const store = configureMockStore(middleware)(mockedStore);
const { container } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />,
store,
);
const recipientElem = container.querySelector(
sendToRecipientSelector,
);
expect(recipientElem).toHaveTextContent(mockTxParamsToAddressConcat);
});
});
describe(`when there is no amount being sent`, () => {
it('should use propToAddress (toAddress passed as prop)', () => {
mockedStoreWithConfirmTxParams({
...mockTxParams,
value: '0x0',
});
const store = configureMockStore(middleware)(mockedStore);
const { container } = renderWithProvider(
<ConfirmTransactionBase
// we want to test toAddress provided by ownProps in mapStateToProps, but this
// currently overrides toAddress this should pan out fine when we refactor the
// component into a functional component and remove the container.js file
toAddress={mockPropsToAddress}
actionKey="confirm"
/>,
store,
);
const recipientElem = container.querySelector(
sendToRecipientSelector,
);
expect(recipientElem).toHaveTextContent(mockPropsToAddressConcat);
});
it('should use address parsed from transaction data if propToAddress is not provided', () => {
mockedStoreWithConfirmTxParams({
...mockTxParams,
value: '0x0',
});
const store = configureMockStore(middleware)(mockedStore);
const { container } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />,
store,
);
const recipientElem = container.querySelector(
sendToRecipientSelector,
);
expect(recipientElem).toHaveTextContent(mockParsedTxDataToAddress);
});
it('should use txParams.to if neither propToAddress is not provided nor the transaction data to address were provided', () => {
mockedStoreWithConfirmTxParams({
...mockTxParams,
data: '0x',
value: '0x0',
});
const store = configureMockStore(middleware)(mockedStore);
const { container } = renderWithProvider(
<ConfirmTransactionBase actionKey="confirm" />,
store,
);
const recipientElem = container.querySelector(
sendToRecipientSelector,
);
expect(recipientElem).toHaveTextContent(mockTxParamsToAddressConcat);
});
});
});
});
}); });

View File

@ -72,7 +72,7 @@ export default function FeeCard({
disableEditGasFeeButton disableEditGasFeeButton
rows={[ rows={[
<TransactionDetailItem <TransactionDetailItem
key="gas-item" key="fee-card-gas-item"
detailTitle={ detailTitle={
<> <>
{t('transactionDetailGasHeading')} {t('transactionDetailGasHeading')}