Skip to content
Merged
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
1 change: 1 addition & 0 deletions app/scripts/controllers/transactions/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2274,6 +2274,7 @@ describe('Transaction Controller', function () {
});

it('updates editible params when type changes from simple send to token transfer', async function () {
providerResultStub.eth_getCode = '0xab';
// test update gasFees
await txController.updateEditableParams('1', {
data:
Expand Down
38 changes: 20 additions & 18 deletions shared/modules/transaction.utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,33 +148,35 @@ export async function determineTransactionType(txParams, query) {
log.debug('Failed to parse transaction data.', error, data);
}

const tokenMethodName = [
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
].find((methodName) => isEqualCaseInsensitive(methodName, name));

let result;
if (data && tokenMethodName) {
result = tokenMethodName;
} else if (data && !to) {
result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
}

let contractCode;

if (!result) {
if (data && !to) {
result = TRANSACTION_TYPES.DEPLOY_CONTRACT;
} else {
const {
contractCode: resultCode,
isContractAddress,
} = await readAddressAsContract(query, to);

contractCode = resultCode;
result = isContractAddress
? TRANSACTION_TYPES.CONTRACT_INTERACTION
: TRANSACTION_TYPES.SIMPLE_SEND;

if (isContractAddress) {
const tokenMethodName = [
TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
TRANSACTION_TYPES.TOKEN_METHOD_SET_APPROVAL_FOR_ALL,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER_FROM,
TRANSACTION_TYPES.TOKEN_METHOD_SAFE_TRANSFER_FROM,
].find((methodName) => isEqualCaseInsensitive(methodName, name));

result =
data && tokenMethodName
? tokenMethodName
: TRANSACTION_TYPES.CONTRACT_INTERACTION;
} else {
result = TRANSACTION_TYPES.SIMPLE_SEND;
}
}

return { type: result, getCodeResponse: contractCode };
Expand Down
89 changes: 77 additions & 12 deletions shared/modules/transaction.utils.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,47 +111,102 @@ describe('Transaction.utils', function () {
const genericProvider = createTestProviderTools().provider;
const query = new EthQuery(genericProvider);

it('should return a simple send type when to is truthy but data is falsy', async function () {
it('should return a simple send type when to is truthy and is not a contract address', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0x',
};
const _provider = createTestProviderTools({
scaffold: _providerResultStub,
}).provider;

const result = await determineTransactionType(
{
to: '0xabc',
data: '',
},
query,
new EthQuery(_provider),
);
expect(result).toMatchObject({
type: TRANSACTION_TYPES.SIMPLE_SEND,
getCodeResponse: null,
});
});

it('should return a token transfer type when data is for the respective method call', async function () {
it('should return a token transfer type when the recipient is a contract and data is for the respective method call', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0xab',
};
const _provider = createTestProviderTools({
scaffold: _providerResultStub,
}).provider;

const result = await determineTransactionType(
{
to: '0xabc',
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data:
'0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
},
query,
new EthQuery(_provider),
);
expect(result).toMatchObject({
type: TRANSACTION_TYPES.TOKEN_METHOD_TRANSFER,
getCodeResponse: undefined,
getCodeResponse: '0xab',
});
});

it('should return a token approve type when data is for the respective method call', async function () {
it('should NOT return a token transfer type when the recipient is not a contract but the data matches the respective method call', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0x',
};
const _provider = createTestProviderTools({
scaffold: _providerResultStub,
}).provider;

const result = await determineTransactionType(
{
to: '0xabc',
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data:
'0xa9059cbb0000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C970000000000000000000000000000000000000000000000000000000000000000a',
},
new EthQuery(_provider),
);
expect(result).toMatchObject({
type: TRANSACTION_TYPES.SIMPLE_SEND,
getCodeResponse: '0x',
});
});

it('should return a token approve type when when the recipient is a contract and data is for the respective method call', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: '0xab',
};
const _provider = createTestProviderTools({
scaffold: _providerResultStub,
}).provider;

const result = await determineTransactionType(
{
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data:
'0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005',
},
query,
new EthQuery(_provider),
);
expect(result).toMatchObject({
type: TRANSACTION_TYPES.TOKEN_METHOD_APPROVE,
getCodeResponse: undefined,
getCodeResponse: '0xab',
});
});

Expand Down Expand Up @@ -184,12 +239,22 @@ describe('Transaction.utils', function () {
});

it('should return a simple send type with a null getCodeResponse when to is truthy and there is data and but getCode returns an error', async function () {
const _providerResultStub = {
// 1 gwei
eth_gasPrice: '0x0de0b6b3a7640000',
// by default, all accounts are external accounts (not contracts)
eth_getCode: null,
};
const _provider = createTestProviderTools({
scaffold: _providerResultStub,
}).provider;

const result = await determineTransactionType(
{
to: '0xabc',
to: '0x9e673399f795D01116e9A8B2dD2F156705131ee9',
data: '0xabd',
},
query,
new EthQuery(_provider),
);
expect(result).toMatchObject({
type: TRANSACTION_TYPES.SIMPLE_SEND,
Expand Down
146 changes: 146 additions & 0 deletions test/e2e/fixtures/special-settings/state.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{
"data": {
"AppStateController": {
"mkrMigrationReminderTimestamp": null
},
"CachedBalancesController": {
"cachedBalances": {
"4": {}
}
},
"CurrencyController": {
"conversionDate": 1575697244.188,
"conversionRate": 149.61,
"currentCurrency": "usd",
"nativeCurrency": "ETH"
},
"IncomingTransactionsController": {
"incomingTransactions": {},
"incomingTxLastFetchedBlocksByNetwork": {
"goerli": null,
"kovan": null,
"mainnet": null,
"rinkeby": 5570536
}
},
"KeyringController": {
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}"
},
"NetworkController": {
"network": "1337",
"provider": {
"nickname": "Localhost 8545",
"rpcUrl": "http://localhost:8545",
"chainId": "0x539",
"ticker": "ETH",
"type": "rpc"
}
},
"NotificationController": {
"notifications": {
"1": {
"isShown": true
},
"3": {
"isShown": true
},
"5": {
"isShown": true
},
"6": {
"isShown": true
},
"8": {
"isShown": true
},
"12": {
"isShown": true
}
}
},
"OnboardingController": {
"onboardingTabs": {},
"seedPhraseBackedUp": false
},
"PermissionsMetadata": {
"domainMetadata": {
"metamask.github.io": {
"icon": null,
"name": "M E T A M A S K M E S H T E S T"
}
},
"permissionsHistory": {},
"permissionsLog": [
{
"id": 746677923,
"method": "eth_accounts",
"methodType": "restricted",
"origin": "metamask.github.io",
"request": {
"id": 746677923,
"jsonrpc": "2.0",
"method": "eth_accounts",
"origin": "metamask.github.io",
"params": []
},
"requestTime": 1575697241368,
"response": {
"id": 746677923,
"jsonrpc": "2.0",
"result": []
},
"responseTime": 1575697241370,
"success": true
}
]
},
"PreferencesController": {
"accountTokens": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"rinkeby": [],
"ropsten": []
}
},
"assetImages": {},
"completedOnboarding": true,
"dismissSeedBackUpReminder": true,
"currentLocale": "en",
"featureFlags": {
"showIncomingTransactions": true,
"transactionTime": false,
"sendHexData": true
},
"firstTimeFlowType": "create",
"forgottenPassword": false,
"frequentRpcListDetail": [],
"identities": {
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": {
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"name": "Account 1"
}
},
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true
},
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1",
"suggestedTokens": {},
"tokens": [],
"useBlockie": false,
"useNonceField": false,
"usePhishDetect": true,
"useTokenDetection": true
},
"config": {},
"firstTimeInfo": {
"date": 1575697234195,
"version": "7.7.0"
}
},
"meta": {
"version": 40
}
}
Loading