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

Merge pull request #11423 from MetaMask/Version-v9.7.1

Version v9.7.1 RC
This commit is contained in:
ryanml 2021-07-01 12:56:25 -07:00 committed by GitHub
commit bd38b02d8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 714 additions and 278 deletions

View File

@ -108,7 +108,11 @@ module.exports = {
},
{
files: ['**/*.test.js'],
excludedFiles: ['ui/**/*.test.js', 'ui/__mocks__/*.js'],
excludedFiles: [
'ui/**/*.test.js',
'ui/__mocks__/*.js',
'shared/**/*.test.js',
],
extends: ['@metamask/eslint-config-mocha'],
rules: {
'mocha/no-setup-in-describe': 'off',
@ -125,7 +129,7 @@ module.exports = {
},
},
{
files: ['ui/**/*.test.js', 'ui/__mocks__/*.js'],
files: ['ui/**/*.test.js', 'ui/__mocks__/*.js', 'shared/**/*.test.js'],
extends: ['@metamask/eslint-config-jest'],
rules: {
'jest/no-restricted-matchers': 'off',

View File

@ -140,7 +140,6 @@ const state = {
}
},
"participateInMetaMetrics": true,
"metaMetricsSendCount": 2,
"nextNonce": 71,
"connectedStatusPopoverHasBeenShown": true,
"swapsWelcomeMessageHasBeenShown": true,

View File

@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [9.7.1]
### Fixed
- [#11426](https://github.com/MetaMask/metamask-extension/pull/11426): Fixed bug that broke transaction speed up and cancel, when attempting those actions immediately after opening MetaMask
## [9.7.0]
### Added
- [#11021](https://github.com/MetaMask/metamask-extension/pull/11021): Add periodic reminder modal for backing up recovery phrase
@ -2317,7 +2321,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Uncategorized
- Added the ability to restore accounts from seed words.
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v9.7.0...HEAD
[Unreleased]: https://github.com/MetaMask/metamask-extension/compare/v9.7.1...HEAD
[9.7.1]: https://github.com/MetaMask/metamask-extension/compare/v9.7.0...v9.7.1
[9.7.0]: https://github.com/MetaMask/metamask-extension/compare/v9.6.1...v9.7.0
[9.6.1]: https://github.com/MetaMask/metamask-extension/compare/v9.6.0...v9.6.1
[9.6.0]: https://github.com/MetaMask/metamask-extension/compare/v9.5.9...v9.6.0

View File

@ -7,30 +7,6 @@ import {
METAMETRICS_BACKGROUND_PAGE_OBJECT,
} from '../../../shared/constants/metametrics';
/**
* Used to determine whether or not to attach a user's metametrics id
* to events that include on-chain data. This helps to prevent identifying
* a user by being able to trace their activity on etherscan/block exploring
*/
const trackableSendCounts = {
1: true,
10: true,
30: true,
50: true,
100: true,
250: true,
500: true,
1000: true,
2500: true,
5000: true,
10000: true,
25000: true,
};
export function sendCountIsTrackable(sendCount) {
return Boolean(trackableSendCounts[sendCount]);
}
/**
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsContext} MetaMetricsContext
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload
@ -48,9 +24,6 @@ export function sendCountIsTrackable(sendCount) {
* @property {?boolean} participateInMetaMetrics - The user's preference for
* participating in the MetaMetrics analytics program. This setting controls
* whether or not events are tracked
* @property {number} metaMetricsSendCount - How many send transactions have
* been tracked through this controller. Used to prevent attaching sensitive
* data that can be traced through on chain data.
*/
export default class MetaMetricsController {
@ -89,7 +62,6 @@ export default class MetaMetricsController {
this.store = new ObservableStore({
participateInMetaMetrics: null,
metaMetricsId: null,
metaMetricsSendCount: 0,
...initState,
});
@ -138,10 +110,6 @@ export default class MetaMetricsController {
return this.store.getState();
}
setMetaMetricsSendCount(val) {
this.store.updateState({ metaMetricsSendCount: val });
}
/**
* Build the context object to attach to page and track events.
* @private
@ -231,11 +199,7 @@ export default class MetaMetricsController {
// to be updated to work with the new tracking plan. I think we should use
// a config setting for this instead of trying to match the event name
const isSendFlow = Boolean(payload.event.match(/^send|^confirm/iu));
if (
isSendFlow &&
this.state.metaMetricsSendCount &&
!sendCountIsTrackable(this.state.metaMetricsSendCount + 1)
) {
if (isSendFlow) {
excludeMetaMetricsId = true;
}
// If we are tracking sensitive data we will always use the anonymousId

View File

@ -84,7 +84,6 @@ function getMockPreferencesStore({ currentLocale = LOCALE } = {}) {
function getMetaMetricsController({
participateInMetaMetrics = true,
metaMetricsId = TEST_META_METRICS_ID,
metaMetricsSendCount = 0,
preferencesStore = getMockPreferencesStore(),
networkController = getMockNetworkController(),
} = {}) {
@ -106,7 +105,6 @@ function getMetaMetricsController({
initState: {
participateInMetaMetrics,
metaMetricsId,
metaMetricsSendCount,
},
});
}
@ -198,14 +196,6 @@ describe('MetaMetricsController', function () {
});
});
describe('setMetaMetricsSendCount', function () {
it('should update the send count in state', function () {
const metaMetricsController = getMetaMetricsController();
metaMetricsController.setMetaMetricsSendCount(1);
assert.equal(metaMetricsController.state.metaMetricsSendCount, 1);
});
});
describe('trackEvent', function () {
it('should not track an event if user is not participating in metametrics', function () {
const mock = sinon.mock(segment);
@ -337,61 +327,6 @@ describe('MetaMetricsController', function () {
mock.verify();
});
it('should use anonymousId when metametrics send count is not trackable in send flow', function () {
const mock = sinon.mock(segment);
const metaMetricsController = getMetaMetricsController({
metaMetricsSendCount: 1,
});
mock
.expects('track')
.once()
.withArgs({
event: 'Send Fake Event',
anonymousId: METAMETRICS_ANONYMOUS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
});
metaMetricsController.trackEvent({
event: 'Send Fake Event',
category: 'Unit Test',
properties: {
test: 1,
},
});
mock.verify();
});
it('should use user metametrics id when metametrics send count is trackable in send flow', function () {
const mock = sinon.mock(segment);
const metaMetricsController = getMetaMetricsController();
mock
.expects('track')
.once()
.withArgs({
event: 'Send Fake Event',
userId: TEST_META_METRICS_ID,
context: DEFAULT_TEST_CONTEXT,
properties: {
test: 1,
...DEFAULT_EVENT_PROPERTIES,
},
});
metaMetricsController.trackEvent(
{
event: 'Send Fake Event',
category: 'Unit Test',
properties: {
test: 1,
},
},
{ metaMetricsSendCount: 0 },
);
mock.verify();
});
it('should immediately flush queue if flushImmediately set to true', async function () {
const metaMetricsController = getMetaMetricsController();
const flushStub = sinon.stub(segment, 'flush');

View File

@ -285,7 +285,7 @@ export default class PreferencesController {
*/
syncAddresses(addresses) {
if (!Array.isArray(addresses) || addresses.length === 0) {
throw new Error('Expected non-empty array of addresses.');
throw new Error('Expected non-empty array of addresses. Error #11201');
}
const { identities, lostIdentities } = this.store.getState();

View File

@ -24,6 +24,7 @@ import {
} from '../../../../shared/constants/transaction';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import { GAS_LIMITS } from '../../../../shared/constants/gas';
import { isEIP1559Transaction } from '../../../../shared/modules/transaction.utils';
import TransactionStateManager from './tx-state-manager';
import TxGasUtil from './tx-gas-utils';
import PendingTransactionTracker from './pending-tx-tracker';
@ -33,6 +34,14 @@ const hstInterface = new ethers.utils.Interface(abi);
const MAX_MEMSTORE_TX_LIST_SIZE = 100; // Number of transactions (by unique nonces) to keep in memory
export const TRANSACTION_EVENTS = {
ADDED: 'Transaction Added',
APPROVED: 'Transaction Approved',
FINALIZED: 'Transaction Finalized',
REJECTED: 'Transaction Rejected',
SUBMITTED: 'Transaction Submitted',
};
/**
Transaction Controller is an aggregate of sub-controllers and trackers
composing them in a way to be exposed to the metamask controller
@ -153,6 +162,7 @@ export default class TransactionController extends EventEmitter {
addTransaction(txMeta) {
this.txStateManager.addTransaction(txMeta);
this.emit(`${txMeta.id}:unapproved`, txMeta);
this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.ADDED);
}
/**
@ -532,12 +542,13 @@ export default class TransactionController extends EventEmitter {
// sign transaction
const rawTx = await this.signTransaction(txId);
await this.publishTransaction(txId, rawTx);
this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.APPROVED);
// must set transaction to submitted/failed before releasing lock
nonceLock.releaseLock();
} catch (err) {
// this is try-catch wrapped so that we can guarantee that the nonceLock is released
try {
this.txStateManager.setTxStatusFailed(txId, err);
this._failTransaction(txId, err);
} catch (err2) {
log.error(err2);
}
@ -615,6 +626,8 @@ export default class TransactionController extends EventEmitter {
this.setTxHash(txId, txHash);
this.txStateManager.setTxStatusSubmitted(txId);
this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.SUBMITTED);
}
/**
@ -647,6 +660,29 @@ export default class TransactionController extends EventEmitter {
this.txStateManager.setTxStatusConfirmed(txId);
this._markNonceDuplicatesDropped(txId);
const { submittedTime } = txMeta;
const { blockNumber } = txReceipt;
const metricsParams = { gas_used: gasUsed };
const completionTime = await this._getTransactionCompletionTime(
blockNumber,
submittedTime,
);
if (completionTime) {
metricsParams.completion_time = completionTime;
}
if (txReceipt.status === '0x0') {
metricsParams.status = 'failed on-chain';
// metricsParams.error = TODO: figure out a way to get the on-chain failure reason
}
this._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.FINALIZED,
metricsParams,
);
this.txStateManager.updateTransaction(
txMeta,
'transactions#confirmTransaction - add txReceipt',
@ -680,7 +716,9 @@ export default class TransactionController extends EventEmitter {
@returns {Promise<void>}
*/
async cancelTransaction(txId) {
const txMeta = this.txStateManager.getTransaction(txId);
this.txStateManager.setTxStatusRejected(txId);
this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.REJECTED);
}
/**
@ -763,7 +801,7 @@ export default class TransactionController extends EventEmitter {
txMeta,
'failed to estimate gas during boot cleanup.',
);
this.txStateManager.setTxStatusFailed(txMeta.id, error);
this._failTransaction(txMeta.id, error);
});
});
@ -777,7 +815,7 @@ export default class TransactionController extends EventEmitter {
const txSignError = new Error(
'Transaction found as "approved" during boot - possibly stuck during signing',
);
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError);
this._failTransaction(txMeta.id, txSignError);
});
}
@ -797,17 +835,15 @@ export default class TransactionController extends EventEmitter {
'transactions/pending-tx-tracker#event: tx:warning',
);
});
this.pendingTxTracker.on(
'tx:failed',
this.txStateManager.setTxStatusFailed.bind(this.txStateManager),
);
this.pendingTxTracker.on('tx:failed', (txId, error) => {
this._failTransaction(txId, error);
});
this.pendingTxTracker.on('tx:confirmed', (txId, transactionReceipt) =>
this.confirmTransaction(txId, transactionReceipt),
);
this.pendingTxTracker.on(
'tx:dropped',
this.txStateManager.setTxStatusDropped.bind(this.txStateManager),
);
this.pendingTxTracker.on('tx:dropped', (txId) => {
this._dropTransaction(txId);
});
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
if (!txMeta.firstRetryBlockNumber) {
txMeta.firstRetryBlockNumber = latestBlockNumber;
@ -917,7 +953,7 @@ export default class TransactionController extends EventEmitter {
txMeta,
'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce',
);
this.txStateManager.setTxStatusDropped(otherTxMeta.id);
this._dropTransaction(otherTxMeta.id);
});
}
@ -1010,4 +1046,84 @@ export default class TransactionController extends EventEmitter {
}
}
}
/**
* Extracts relevant properties from a transaction meta
* object and uses them to create and send metrics for various transaction
* events.
* @param {Object} txMeta - the txMeta object
* @param {string} event - the name of the transaction event
* @param {Object} extraParams - optional props and values to include in sensitiveProperties
*/
_trackTransactionMetricsEvent(txMeta, event, extraParams = {}) {
const {
type,
time,
status,
chainId,
origin: referrer,
txParams: { gasPrice, gas: gasLimit, maxFeePerGas, maxPriorityFeePerGas },
metamaskNetworkId: network,
} = txMeta;
const source = referrer === 'metamask' ? 'user' : 'dapp';
const gasParams = {};
if (isEIP1559Transaction(txMeta)) {
gasParams.max_fee_per_gas = maxFeePerGas;
gasParams.max_priority_fee_per_gas = maxPriorityFeePerGas;
} else {
gasParams.gas_price = gasPrice;
}
this._trackMetaMetricsEvent({
event,
category: 'Transactions',
sensitiveProperties: {
type,
status,
referrer,
source,
network,
chain_id: chainId,
transaction_envelope_type: isEIP1559Transaction(txMeta)
? 'fee-market'
: 'legacy',
first_seen: time,
gas_limit: gasLimit,
...gasParams,
...extraParams,
},
});
}
async _getTransactionCompletionTime(blockNumber, submittedTime) {
const transactionBlock = await this.query.getBlockByNumber(
blockNumber.toString(16),
false,
);
if (!transactionBlock) {
return '';
}
return new BigNumber(transactionBlock.timestamp, 10)
.minus(submittedTime / 1000)
.round()
.toString(10);
}
_failTransaction(txId, error) {
this.txStateManager.setTxStatusFailed(txId, error);
const txMeta = this.txStateManager.getTransaction(txId);
this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.FINALIZED, {
error: error.message,
});
}
_dropTransaction(txId) {
this.txStateManager.setTxStatusDropped(txId);
const txMeta = this.txStateManager.getTransaction(txId);
this._trackTransactionMetricsEvent(txMeta, TRANSACTION_EVENTS.FINALIZED);
}
}

View File

@ -15,7 +15,7 @@ import {
} from '../../../../shared/constants/transaction';
import { SECOND } from '../../../../shared/constants/time';
import { METAMASK_CONTROLLER_EVENTS } from '../../metamask-controller';
import TransactionController from '.';
import TransactionController, { TRANSACTION_EVENTS } from '.';
const noop = () => true;
const currentNetworkId = '42';
@ -56,6 +56,7 @@ describe('Transaction Controller', function () {
getPermittedAccounts: () => undefined,
getCurrentChainId: () => currentChainId,
getParticipateInMetrics: () => false,
trackMetaMetricsEvent: () => undefined,
});
txController.nonceTracker.getNonceLock = () =>
Promise.resolve({ nextNonce: 0, releaseLock: noop });
@ -414,6 +415,19 @@ describe('Transaction Controller', function () {
});
describe('#addTransaction', function () {
let trackTransactionMetricsEventSpy;
beforeEach(function () {
trackTransactionMetricsEventSpy = sinon.spy(
txController,
'_trackTransactionMetricsEvent',
);
});
afterEach(function () {
trackTransactionMetricsEventSpy.restore();
});
it('should emit updates', function (done) {
const txMeta = {
id: '1',
@ -451,6 +465,38 @@ describe('Transaction Controller', function () {
.catch(done);
txController.addTransaction(txMeta);
});
it('should call _trackTransactionMetricsEvent with the correct params', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: 'sentEther',
transaction_envelope_type: 'legacy',
origin: 'metamask',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
txController.addTransaction(txMeta);
assert.equal(trackTransactionMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackTransactionMetricsEventSpy.getCall(0).args[0],
txMeta,
);
assert.equal(
trackTransactionMetricsEventSpy.getCall(0).args[1],
TRANSACTION_EVENTS.ADDED,
);
});
});
describe('#approveTransaction', function () {
@ -723,7 +769,8 @@ describe('Transaction Controller', function () {
});
describe('#publishTransaction', function () {
let hash, txMeta;
let hash, txMeta, trackTransactionMetricsEventSpy;
beforeEach(function () {
hash =
'0x2a5523c6fa98b47b7d9b6c8320179785150b42a16bcff36b398c5062b65657e8';
@ -731,12 +778,21 @@ describe('Transaction Controller', function () {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
gas: '0x7b0d',
to: VALID_ADDRESS,
from: VALID_ADDRESS_TWO,
},
metamaskNetworkId: currentNetworkId,
};
providerResultStub.eth_sendRawTransaction = hash;
trackTransactionMetricsEventSpy = sinon.spy(
txController,
'_trackTransactionMetricsEvent',
);
});
afterEach(function () {
trackTransactionMetricsEventSpy.restore();
});
it('should publish a tx, updates the rawTx when provided a one', async function () {
@ -764,6 +820,22 @@ describe('Transaction Controller', function () {
);
assert.equal(publishedTx.status, TRANSACTION_STATUSES.SUBMITTED);
});
it('should call _trackTransactionMetricsEvent with the correct params', async function () {
const rawTx =
'0x477b2e6553c917af0db0388ae3da62965ff1a184558f61b749d1266b2e6d024c';
txController.txStateManager.addTransaction(txMeta);
await txController.publishTransaction(txMeta.id, rawTx);
assert.equal(trackTransactionMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackTransactionMetricsEventSpy.getCall(0).args[0],
txMeta,
);
assert.equal(
trackTransactionMetricsEventSpy.getCall(0).args[1],
TRANSACTION_EVENTS.SUBMITTED,
);
});
});
describe('#_markNonceDuplicatesDropped', function () {
@ -1105,4 +1177,213 @@ describe('Transaction Controller', function () {
);
});
});
describe('#_trackTransactionMetricsEvent', function () {
let trackMetaMetricsEventSpy;
beforeEach(function () {
trackMetaMetricsEventSpy = sinon.spy(
txController,
'_trackMetaMetricsEvent',
);
});
afterEach(function () {
trackMetaMetricsEventSpy.restore();
});
it('should call _trackMetaMetricsEvent with the correct payload (user source)', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: 'sentEther',
origin: 'metamask',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
category: 'Transactions',
sensitiveProperties: {
chain_id: '0x2a',
gas_price: '0x77359400',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: 'legacy',
network: '42',
referrer: 'metamask',
source: 'user',
status: 'unapproved',
type: 'sentEther',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
expectedPayload,
);
});
it('should call _trackMetaMetricsEvent with the correct payload (dapp source)', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: 'sentEther',
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
category: 'Transactions',
sensitiveProperties: {
chain_id: '0x2a',
gas_price: '0x77359400',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: 'legacy',
network: '42',
referrer: 'other',
source: 'dapp',
status: 'unapproved',
type: 'sentEther',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
expectedPayload,
);
});
it('should call _trackMetaMetricsEvent with the correct payload (extra params)', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
gasPrice: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: 'sentEther',
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
category: 'Transactions',
sensitiveProperties: {
baz: 3.0,
foo: 'bar',
chain_id: '0x2a',
gas_price: '0x77359400',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: 'legacy',
network: '42',
referrer: 'other',
source: 'dapp',
status: 'unapproved',
type: 'sentEther',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
{
baz: 3.0,
foo: 'bar',
},
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
expectedPayload,
);
});
it('should call _trackMetaMetricsEvent with the correct payload (EIP-1559)', function () {
const txMeta = {
id: 1,
status: TRANSACTION_STATUSES.UNAPPROVED,
txParams: {
from: fromAccount.address,
to: '0x1678a085c290ebd122dc42cba69373b5953b831d',
maxFeePerGas: '0x77359400',
maxPriorityFeePerGas: '0x77359400',
gas: '0x7b0d',
nonce: '0x4b',
},
type: 'sentEther',
origin: 'other',
chainId: currentChainId,
time: 1624408066355,
metamaskNetworkId: currentNetworkId,
};
const expectedPayload = {
event: 'Transaction Added',
category: 'Transactions',
sensitiveProperties: {
baz: 3.0,
foo: 'bar',
chain_id: '0x2a',
max_fee_per_gas: '0x77359400',
max_priority_fee_per_gas: '0x77359400',
gas_limit: '0x7b0d',
first_seen: 1624408066355,
transaction_envelope_type: 'fee-market',
network: '42',
referrer: 'other',
source: 'dapp',
status: 'unapproved',
type: 'sentEther',
},
};
txController._trackTransactionMetricsEvent(
txMeta,
TRANSACTION_EVENTS.ADDED,
{
baz: 3.0,
foo: 'bar',
},
);
assert.equal(trackMetaMetricsEventSpy.callCount, 1);
assert.deepEqual(
trackMetaMetricsEventSpy.getCall(0).args[0],
expectedPayload,
);
});
});
});

View File

@ -36,7 +36,6 @@ export const SENTRY_STATE = {
isInitialized: true,
isUnlocked: true,
metaMetricsId: true,
metaMetricsSendCount: true,
nativeCurrency: true,
network: true,
nextNonce: true,

View File

@ -676,7 +676,6 @@ export default class MetamaskController extends EventEmitter {
setUsePhishDetect: this.setUsePhishDetect.bind(this),
setIpfsGateway: this.setIpfsGateway.bind(this),
setParticipateInMetaMetrics: this.setParticipateInMetaMetrics.bind(this),
setMetaMetricsSendCount: this.setMetaMetricsSendCount.bind(this),
setFirstTimeFlowType: this.setFirstTimeFlowType.bind(this),
setCurrentLocale: this.setCurrentLocale.bind(this),
markPasswordForgotten: this.markPasswordForgotten.bind(this),
@ -2760,18 +2759,6 @@ export default class MetamaskController extends EventEmitter {
}
}
setMetaMetricsSendCount(val, cb) {
try {
this.metaMetricsController.setMetaMetricsSendCount(val);
cb(null);
return;
} catch (err) {
cb(err);
// eslint-disable-next-line no-useless-return
return;
}
}
/**
* Sets the type of first time flow the user wishes to follow: create or import
* @param {string} type - Indicates the type of first time flow the user wishes to follow

View File

@ -0,0 +1,28 @@
import { cloneDeep } from 'lodash';
const version = 62;
/**
* Removes metaMetricsSendCount from MetaMetrics controller
*/
export default {
version,
async migrate(originalVersionedData) {
const versionedData = cloneDeep(originalVersionedData);
versionedData.meta.version = version;
const state = versionedData.data;
const newState = transformState(state);
versionedData.data = newState;
return versionedData;
},
};
function transformState(state) {
if (state.MetaMetricsController) {
const { metaMetricsSendCount } = state.MetaMetricsController;
if (metaMetricsSendCount !== undefined) {
delete state.MetaMetricsController.metaMetricsSendCount;
}
}
return state;
}

View File

@ -0,0 +1,80 @@
import { strict as assert } from 'assert';
import migration62 from './062';
describe('migration #62', function () {
it('should update the version metadata', async function () {
const oldStorage = {
meta: {
version: 61,
},
data: {},
};
const newStorage = await migration62.migrate(oldStorage);
assert.deepEqual(newStorage.meta, {
version: 62,
});
});
it('should remove metaMetricsSendCount from MetaMetricsController', async function () {
const oldStorage = {
meta: {},
data: {
MetaMetricsController: {
metaMetricsSendCount: 1,
bar: 'baz',
},
foo: 'bar',
},
};
const newStorage = await migration62.migrate(oldStorage);
assert.deepStrictEqual(newStorage.data, {
MetaMetricsController: {
bar: 'baz',
},
foo: 'bar',
});
});
it('should remove metaMetricsSendCount from MetaMetricsController (falsey but defined)', async function () {
const oldStorage = {
meta: {},
data: {
MetaMetricsController: {
metaMetricsSendCount: 0,
bar: 'baz',
},
foo: 'bar',
},
};
const newStorage = await migration62.migrate(oldStorage);
assert.deepStrictEqual(newStorage.data, {
MetaMetricsController: {
bar: 'baz',
},
foo: 'bar',
});
});
it('should not modify MetaMetricsController when metaMetricsSendCount is undefined', async function () {
const oldStorage = {
meta: {},
data: {
MetaMetricsController: {
bar: 'baz',
},
foo: 'bar',
},
};
const newStorage = await migration62.migrate(oldStorage);
assert.deepStrictEqual(newStorage.data, {
MetaMetricsController: {
bar: 'baz',
},
foo: 'bar',
});
});
});

View File

@ -66,6 +66,7 @@ const migrations = [
require('./059').default,
require('./060').default,
require('./061').default,
require('./062').default,
];
export default migrations;

View File

@ -13,5 +13,8 @@ module.exports = {
},
setupFiles: ['./test/setup.js', './test/env.js'],
setupFilesAfterEnv: ['./test/jest/setup.js'],
testMatch: ['**/ui/**/?(*.)+(test).js'],
testMatch: [
'<rootDir>/ui/**/?(*.)+(test).js',
'<rootDir>/shared/**/?(*.)+(test).js',
],
};

View File

@ -1,6 +1,6 @@
{
"name": "metamask-crx",
"version": "9.7.0",
"version": "9.7.1",
"private": true,
"repository": {
"type": "git",
@ -23,13 +23,13 @@
"dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'",
"forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010",
"dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'",
"test:unit": "mocha --exit --require test/env.js --require test/setup.js --recursive './{app,shared}/**/*.test.js'",
"test:unit": "mocha --exit --require test/env.js --require test/setup.js --recursive './app/**/*.test.js'",
"test:unit:global": "mocha --exit --require test/env.js --require test/setup.js --recursive test/unit-global/*.test.js",
"test:unit:jest": "jest",
"test:unit:jest:watch": "jest --watch",
"test:unit:jest:watch:silent": "jest --watch --silent",
"test:unit:jest:ci": "jest --maxWorkers=2",
"test:unit:lax": "mocha --exit --require test/env.js --require test/setup.js --ignore './app/scripts/controllers/permissions/*.test.js' --recursive './{app,shared}/**/*.test.js'",
"test:unit:lax": "mocha --exit --require test/env.js --require test/setup.js --ignore './app/scripts/controllers/permissions/*.test.js' --recursive './app/**/*.test.js'",
"test:unit:strict": "mocha --exit --require test/env.js --require test/setup.js --recursive './app/scripts/controllers/permissions/*.test.js'",
"test:unit:path": "mocha --exit --require test/env.js --require test/setup.js --recursive",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome test/e2e/run-all.sh",
@ -53,7 +53,7 @@
"verify-locales": "node ./development/verify-locale-strings.js",
"verify-locales:fix": "node ./development/verify-locale-strings.js --fix",
"mozilla-lint": "addons-linter dist/firefox",
"watch": "mocha --watch --require test/env.js --require test/setup.js --reporter min --recursive \"test/unit/**/*.js\" \"ui/**/*.test.js\" \"shared/**/*.test.js\"",
"watch": "mocha --watch --require test/env.js --require test/setup.js --reporter min --recursive \"test/unit/**/*.js\" \"ui/**/*.test.js\"",
"devtools:react": "react-devtools",
"devtools:redux": "remotedev --hostname=localhost --port=8000",
"start:dev": "concurrently -k -n build,react,redux yarn:start yarn:devtools:react yarn:devtools:redux",

View File

@ -1,6 +1,40 @@
import { isHexString } from 'ethereumjs-util';
export function transactionMatchesNetwork(transaction, chainId, networkId) {
if (typeof transaction.chainId !== 'undefined') {
return transaction.chainId === chainId;
}
return transaction.metamaskNetworkId === networkId;
}
/**
* Determines if the maxFeePerGas and maxPriorityFeePerGas fields are supplied
* and valid inputs. This will return false for non hex string inputs.
* @param {import("../constants/transaction").TransactionMeta} transaction -
* the transaction to check
* @returns {boolean} true if transaction uses valid EIP1559 fields
*/
export function isEIP1559Transaction(transaction) {
return (
isHexString(transaction.txParams.maxFeePerGas) &&
isHexString(transaction.txParams.maxPriorityFeePerGas)
);
}
/**
* Determine if the maxFeePerGas and maxPriorityFeePerGas fields are not
* supplied and that the gasPrice field is valid if it is provided. This will
* return false if gasPrice is a non hex string.
* @param {import("../constants/transaction").TransactionMeta} transaction -
* the transaction to check
* @returns {boolean} true if transaction uses valid Legacy fields OR lacks
* EIP1559 fields
*/
export function isLegacyTransaction(transaction) {
return (
typeof transaction.txParams.maxFeePerGas === 'undefined' &&
typeof transaction.txParams.maxPriorityFeePerGas === 'undefined' &&
(typeof transaction.txParams.gasPrice === 'undefined' ||
isHexString(transaction.txParams.gasPrice))
);
}

View File

@ -0,0 +1,83 @@
import { isEIP1559Transaction, isLegacyTransaction } from './transaction.utils';
describe('Transaction.utils', function () {
describe('isEIP1559Transaction', function () {
it('should return true if both maxFeePerGas and maxPriorityFeePerGas are hex strings', () => {
expect(
isEIP1559Transaction({
txParams: { maxFeePerGas: '0x1', maxPriorityFeePerGas: '0x1' },
}),
).toBe(true);
});
it('should return false if either maxFeePerGas and maxPriorityFeePerGas are non-hex strings', () => {
expect(
isEIP1559Transaction({
txParams: { maxFeePerGas: 0, maxPriorityFeePerGas: '0x1' },
}),
).toBe(false);
expect(
isEIP1559Transaction({
txParams: { maxFeePerGas: '0x1', maxPriorityFeePerGas: 'fail' },
}),
).toBe(false);
});
it('should return false if either maxFeePerGas or maxPriorityFeePerGas are not supplied', () => {
expect(
isEIP1559Transaction({
txParams: { maxPriorityFeePerGas: '0x1' },
}),
).toBe(false);
expect(
isEIP1559Transaction({
txParams: { maxFeePerGas: '0x1' },
}),
).toBe(false);
});
});
describe('isLegacyTransaction', function () {
it('should return true if no gas related fields are supplied', () => {
expect(
isLegacyTransaction({
txParams: {},
}),
).toBe(true);
});
it('should return true if gasPrice is solely provided', () => {
expect(
isLegacyTransaction({
txParams: { gasPrice: '0x1' },
}),
).toBe(true);
});
it('should return false if gasPrice is not a hex string', () => {
expect(
isLegacyTransaction({
txParams: { gasPrice: 100 },
}),
).toBe(false);
});
it('should return false if either maxFeePerGas or maxPriorityFeePerGas are supplied', () => {
expect(
isLegacyTransaction({
txParams: {
maxFeePerGas: '0x1',
},
}),
).toBe(false);
expect(
isLegacyTransaction({
txParams: {
maxPriorityFeePerGas: 'any data',
},
}),
).toBe(false);
});
});
});

View File

@ -128,7 +128,6 @@
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true

View File

@ -138,7 +138,6 @@
},
"completedOnboarding": true,
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"ipfsGateway": "dweb.link",
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1"
},

View File

@ -129,7 +129,6 @@
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true

View File

@ -121,7 +121,6 @@
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true

View File

@ -125,8 +125,7 @@
},
"MetaMetricsController": {
"participateInMetaMetrics": false,
"metaMetricsId": null,
"metaMetricsSendCount": 1
"metaMetricsId": null
},
"PermissionsController": {
"permissionsRequests": [],

View File

@ -114,7 +114,6 @@
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true

View File

@ -114,7 +114,6 @@
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"participateInMetaMetrics": false,
"preferences": {
"showFiatInTestnets": true,

View File

@ -138,7 +138,6 @@
},
"completedOnboarding": true,
"metaMetricsId": "fake-metrics-id",
"metaMetricsSendCount": 0,
"ipfsGateway": "dweb.link",
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1"
},

View File

@ -115,7 +115,6 @@
"knownMethodData": {},
"lostIdentities": {},
"metaMetricsId": null,
"metaMetricsSendCount": 0,
"participateInMetaMetrics": false,
"preferences": {
"useNativeCurrencyAsPrimaryCurrency": true

View File

@ -120,8 +120,7 @@
},
"MetaMetricsController": {
"metaMetricsId": null,
"participateInMetaMetrics": false,
"metaMetricsSendCount": 0
"participateInMetaMetrics": false
},
"ThreeBoxController": {
"threeBoxSyncingAllowed": true,

View File

@ -41,6 +41,7 @@ import {
getAveragePriceEstimateInHexWEI,
isCustomPriceExcessive,
getIsGasEstimatesFetched,
getIsCustomNetworkGasPriceFetched,
} from '../../../../selectors';
import {
@ -141,13 +142,17 @@ const mapStateToProps = (state, ownProps) => {
conversionRate,
});
const isGasEstimate = getIsGasEstimatesFetched(state);
const customNetworkEstimateWasFetched = getIsCustomNetworkGasPriceFetched(
state,
);
let customPriceIsSafe;
let customPriceIsSafe = true;
if ((isMainnet || process.env.IN_TEST) && isGasEstimate) {
customPriceIsSafe = isCustomPriceSafe(state);
} else if (isTestnet) {
customPriceIsSafe = true;
} else {
} else if (
!(isMainnet || process.env.IN_TEST || isTestnet) &&
customNetworkEstimateWasFetched
) {
customPriceIsSafe = isCustomPriceSafeForCustomNetwork(state);
}

View File

@ -33,7 +33,6 @@ export default function reduceMetamask(state = {}, action) {
completedOnboarding: false,
knownMethodData: {},
participateInMetaMetrics: null,
metaMetricsSendCount: 0,
nextNonce: null,
conversionRate: null,
nativeCurrency: 'ETH',
@ -126,12 +125,6 @@ export default function reduceMetamask(state = {}, action) {
participateInMetaMetrics: action.value,
};
case actionConstants.SET_METAMETRICS_SEND_COUNT:
return {
...metamaskState,
metaMetricsSendCount: action.value,
};
case actionConstants.SET_USE_BLOCKIE:
return {
...metamaskState,

View File

@ -86,8 +86,6 @@ export default class ConfirmTransactionBase extends Component {
hideSubtitle: PropTypes.bool,
identiconAddress: PropTypes.string,
onEdit: PropTypes.func,
setMetaMetricsSendCount: PropTypes.func,
metaMetricsSendCount: PropTypes.number,
subtitleComponent: PropTypes.node,
title: PropTypes.string,
advancedInlineGasShown: PropTypes.bool,
@ -475,35 +473,16 @@ export default class ConfirmTransactionBase extends Component {
}
handleCancel() {
const { metricsEvent } = this.context;
const {
txData,
cancelTransaction,
history,
mostRecentOverviewPage,
clearConfirmTransaction,
actionKey,
txData: { origin },
methodData = {},
updateCustomNonce,
} = this.props;
this._removeBeforeUnload();
metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Confirm Screen',
name: 'Cancel',
},
customVariables: {
recipientKnown: null,
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
updateCustomNonce('');
cancelTransaction(txData).then(() => {
clearConfirmTransaction();
@ -512,18 +491,12 @@ export default class ConfirmTransactionBase extends Component {
}
handleSubmit() {
const { metricsEvent } = this.context;
const {
txData: { origin },
sendTransaction,
clearConfirmTransaction,
txData,
history,
actionKey,
mostRecentOverviewPage,
metaMetricsSendCount = 0,
setMetaMetricsSendCount,
methodData = {},
updateCustomNonce,
} = this.props;
const { submitting } = this.state;
@ -539,44 +512,27 @@ export default class ConfirmTransactionBase extends Component {
},
() => {
this._removeBeforeUnload();
metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Confirm Screen',
name: 'Transaction Completed',
},
customVariables: {
recipientKnown: null,
functionType:
actionKey ||
getMethodName(methodData.name) ||
TRANSACTION_TYPES.CONTRACT_INTERACTION,
origin,
},
});
setMetaMetricsSendCount(metaMetricsSendCount + 1).then(() => {
sendTransaction(txData)
.then(() => {
clearConfirmTransaction();
this.setState(
{
submitting: false,
},
() => {
history.push(mostRecentOverviewPage);
updateCustomNonce('');
},
);
})
.catch((error) => {
this.setState({
sendTransaction(txData)
.then(() => {
clearConfirmTransaction();
this.setState(
{
submitting: false,
submitError: error.message,
});
updateCustomNonce('');
},
() => {
history.push(mostRecentOverviewPage);
updateCustomNonce('');
},
);
})
.catch((error) => {
this.setState({
submitting: false,
submitError: error.message,
});
});
updateCustomNonce('');
});
},
);
}
@ -643,18 +599,7 @@ export default class ConfirmTransactionBase extends Component {
}
_beforeUnload = () => {
const { txData: { origin, id } = {}, cancelTransaction } = this.props;
const { metricsEvent } = this.context;
metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Confirm Screen',
name: 'Cancel Tx Via Notification Close',
},
customVariables: {
origin,
},
});
const { txData: { id } = {}, cancelTransaction } = this.props;
cancelTransaction({ id });
};

View File

@ -10,7 +10,6 @@ import {
cancelTxs,
updateAndApproveTx,
showModal,
setMetaMetricsSendCount,
updateTransaction,
getNextNonce,
tryReverseResolveAddress,
@ -76,7 +75,6 @@ const mapStateToProps = (state, ownProps) => {
assetImages,
network,
unapprovedTxs,
metaMetricsSendCount,
nextNonce,
provider: { chainId },
} = metamask;
@ -186,7 +184,6 @@ const mapStateToProps = (state, ownProps) => {
insufficientBalance,
hideSubtitle: !isMainnet && !showFiatInTestnets,
hideFiatConversion: !isMainnet && !showFiatInTestnets,
metaMetricsSendCount,
type,
nextNonce,
mostRecentOverviewPage: getMostRecentOverviewPage(state),
@ -234,7 +231,6 @@ export const mapDispatchToProps = (dispatch) => {
cancelAllTransactions: (txList) => dispatch(cancelTxs(txList)),
sendTransaction: (txData) =>
dispatch(updateAndApproveTx(customNonceMerge(txData))),
setMetaMetricsSendCount: (val) => dispatch(setMetaMetricsSendCount(val)),
getNextNonce: () => dispatch(getNextNonce()),
setDefaultHomeActiveTabName: (tabName) =>
dispatch(setDefaultHomeActiveTabName(tabName)),

View File

@ -32,11 +32,11 @@ export default class SendGasRow extends Component {
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
trackEvent: PropTypes.func,
};
renderAdvancedOptionsButton() {
const { metricsEvent } = this.context;
const { trackEvent } = this.context;
const {
showCustomizeGasModal,
isMainnet,
@ -54,12 +54,9 @@ export default class SendGasRow extends Component {
<div
className="advanced-gas-options-btn"
onClick={() => {
metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Edit Screen',
name: 'Clicked "Advanced Options"',
},
trackEvent({
category: 'Transactions',
event: 'Clicked "Advanced Options"',
});
showCustomizeGasModal();
}}
@ -105,7 +102,7 @@ export default class SendGasRow extends Component {
isEthGasPrice,
noGasPrice,
} = this.props;
const { metricsEvent } = this.context;
const { trackEvent } = this.context;
const gasPriceFetchFailure = isEthGasPrice || noGasPrice;
const gasPriceButtonGroup = (
@ -115,11 +112,11 @@ export default class SendGasRow extends Component {
showCheck={false}
{...gasPriceButtonGroupProps}
handleGasPriceSelection={async (opts) => {
metricsEvent({
eventOpts: {
category: 'Transactions',
action: 'Edit Screen',
name: 'Changed Gas Button',
trackEvent({
category: 'Transactions',
event: 'User Clicked Gas Estimate Button',
properties: {
gasEstimateType: opts.gasEstimateType.toLowerCase(),
},
});
await gasPriceButtonGroupProps.handleGasPriceSelection(opts);

View File

@ -32,7 +32,7 @@ describe('SendGasRow Component', () => {
anotherGasPriceButtonGroupProp: 'bar',
}}
/>,
{ context: { t: (str) => `${str}_t`, metricsEvent: () => ({}) } },
{ context: { t: (str) => `${str}_t`, trackEvent: () => ({}) } },
);
wrapper.setProps({ isMainnet: true });
});

View File

@ -109,6 +109,10 @@ export function isCustomPriceSafeForCustomNetwork(state) {
return true;
}
if (!estimatedPrice) {
return false;
}
const customPriceSafe = conversionGreaterThan(
{
value: customGasPrice,
@ -391,6 +395,15 @@ export function getIsEthGasPriceFetched(state) {
);
}
export function getIsCustomNetworkGasPriceFetched(state) {
const gasState = state.gas;
return Boolean(
gasState.estimateSource === GAS_SOURCE.ETHGASPRICE &&
gasState.basicEstimateStatus === BASIC_ESTIMATE_STATES.READY &&
!getIsMainnet(state),
);
}
export function getNoGasPriceFetched(state) {
const gasState = state.gas;
return Boolean(gasState.basicEstimateStatus === BASIC_ESTIMATE_STATES.FAILED);

View File

@ -60,7 +60,6 @@ export const UPDATE_CUSTOM_NONCE = 'UPDATE_CUSTOM_NONCE';
export const SET_IPFS_GATEWAY = 'SET_IPFS_GATEWAY';
export const SET_PARTICIPATE_IN_METAMETRICS = 'SET_PARTICIPATE_IN_METAMETRICS';
export const SET_METAMETRICS_SEND_COUNT = 'SET_METAMETRICS_SEND_COUNT';
// locale
export const SET_CURRENT_LOCALE = 'SET_CURRENT_LOCALE';

View File

@ -1968,27 +1968,6 @@ export function setParticipateInMetaMetrics(val) {
};
}
export function setMetaMetricsSendCount(val) {
return (dispatch) => {
log.debug(`background.setMetaMetricsSendCount`);
return new Promise((resolve, reject) => {
background.setMetaMetricsSendCount(val, (err) => {
if (err) {
dispatch(displayWarning(err.message));
reject(err);
return;
}
dispatch({
type: actionConstants.SET_METAMETRICS_SEND_COUNT,
value: val,
});
resolve(val);
});
});
};
}
export function setUseBlockie(val) {
return (dispatch) => {
dispatch(showLoadingIndication());