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

fix: re-simulate transactions if security checks fail #4792

Merged
merged 29 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
1fc3f8b
Add simulation retrigger
OGPoyraz Oct 14, 2024
59e7d49
Remove log
OGPoyraz Oct 14, 2024
b00c588
Relocate the changeInSimulationData set
OGPoyraz Oct 14, 2024
306b99a
Reassign simulationData into new object
OGPoyraz Oct 14, 2024
3fec6ed
Adjust coverage
OGPoyraz Oct 14, 2024
0571e3c
Fix suggestions
OGPoyraz Oct 15, 2024
75f7376
Fix suggestions
OGPoyraz Oct 15, 2024
e0aafca
Fix deps
OGPoyraz Oct 15, 2024
379a83d
fix types
OGPoyraz Oct 15, 2024
4229621
Merge branch 'main' into 3380-confirmations-fix-for-the-new-simulatio…
OGPoyraz Oct 15, 2024
d662640
Fix lint
OGPoyraz Oct 15, 2024
ef7bb24
Add time property for the second call of simulation
OGPoyraz Oct 16, 2024
aff979d
Merge branch 'main' into 3380-confirmations-fix-for-the-new-simulatio…
OGPoyraz Oct 17, 2024
d2e2794
update recent changes
OGPoyraz Oct 21, 2024
6262575
Merge branch 'main' into 3380-confirmations-fix-for-the-new-simulatio…
OGPoyraz Oct 21, 2024
c7cd81e
Fix lint
OGPoyraz Oct 21, 2024
5dc3f8a
Fix lint
OGPoyraz Oct 21, 2024
c30f1ac
Fix lint
OGPoyraz Oct 21, 2024
07fafdd
Add unit tests for isPercentageDifferenceWithinThreshold
OGPoyraz Oct 21, 2024
3c1aabe
adjust coverage
OGPoyraz Oct 21, 2024
dee63cc
remove comment
OGPoyraz Oct 24, 2024
8518e61
Merge branch 'main' into 3380-confirmations-fix-for-the-new-simulatio…
OGPoyraz Oct 24, 2024
a8e73b9
Create resimulate utils
matthewwalsh0 Oct 25, 2024
d823fc2
Add unit tests
matthewwalsh0 Oct 25, 2024
2e29bf3
Remove bignumber.js package
matthewwalsh0 Oct 25, 2024
c3543e6
Add increase difference unit test
matthewwalsh0 Oct 25, 2024
f6c3e03
Remove clearing simulation data while loading
matthewwalsh0 Oct 25, 2024
3806a69
Prevent simulation loop
matthewwalsh0 Oct 25, 2024
5355397
Merge branch 'main' into 3380-confirmations-fix-for-the-new-simulatio…
OGPoyraz Oct 29, 2024
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
2 changes: 1 addition & 1 deletion packages/notification-services-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"@contentful/rich-text-html-renderer": "^16.5.2",
"@metamask/base-controller": "^7.0.1",
"@metamask/controller-utils": "^11.3.0",
"bignumber.js": "^4.1.0",
"bignumber.js": "^9.1.2",
"firebase": "^10.11.0",
"loglevel": "^1.8.1",
"uuid": "^8.3.2"
Expand Down
1 change: 1 addition & 0 deletions packages/transaction-controller/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"@metamask/rpc-errors": "^7.0.0",
"@metamask/utils": "^9.1.0",
"async-mutex": "^0.5.0",
"bignumber.js": "^9.1.2",
"bn.js": "^5.2.1",
"eth-method-registry": "^4.0.0",
"fast-json-patch": "^3.1.1",
Expand Down
203 changes: 194 additions & 9 deletions packages/transaction-controller/src/TransactionController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { errorCodes, providerErrors, rpcErrors } from '@metamask/rpc-errors';
import type { Hex } from '@metamask/utils';
import { createDeferredPromise } from '@metamask/utils';
import assert from 'assert';
import { merge } from 'lodash';
import * as uuidModule from 'uuid';

import { FakeBlockTracker } from '../../../tests/fake-block-tracker';
Expand Down Expand Up @@ -73,6 +74,7 @@ import type {
SubmitHistoryEntry,
} from './types';
import {
BLOCKAID_RESULT_TYPE_MALICIOUS,
GasFeeEstimateType,
SimulationErrorCode,
SimulationTokenStandard,
Expand Down Expand Up @@ -1817,28 +1819,40 @@ describe('TransactionController', () => {

describe('updates simulation data', () => {
it('by default', async () => {
getSimulationDataMock.mockResolvedValueOnce(SIMULATION_DATA_MOCK);
const modifiedSimulationDataMock = merge({}, SIMULATION_DATA_MOCK, {
nativeBalanceChange: {
difference: '0x1',
},
});

getSimulationDataMock.mockResolvedValueOnce(modifiedSimulationDataMock);

const { controller } = setupController();

await controller.addTransaction({
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
value: '0x1',
});

await flushPromises();

expect(getSimulationDataMock).toHaveBeenCalledTimes(1);
expect(getSimulationDataMock).toHaveBeenCalledWith({
chainId: MOCK_NETWORK.chainId,
data: undefined,
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
value: '0x0',
});
expect(getSimulationDataMock).toHaveBeenCalledWith(
{
chainId: MOCK_NETWORK.chainId,
data: undefined,
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
value: '0x1',
},
{
isReSimulatedDueToSecurity: false,
},
);

expect(controller.state.transactions[0].simulationData).toStrictEqual(
SIMULATION_DATA_MOCK,
modifiedSimulationDataMock,
);
});

Expand Down Expand Up @@ -5826,4 +5840,175 @@ describe('TransactionController', () => {
);
});
});

describe('re-simulates', () => {
it('on updateSecurityAlertResponse when transaction marked as malicious', async () => {
const modifiedTransactionMeta = merge({}, TRANSACTION_META_MOCK, {
txParams: {
value: '0x1',
},
});

const modifiedSimulationData = merge({}, SIMULATION_DATA_MOCK, {
nativeBalanceChange: {
difference: '0x1',
},
});

getSimulationDataMock.mockResolvedValueOnce(modifiedSimulationData);

const { controller } = setupController({
options: {
state: {
transactions: [modifiedTransactionMeta],
},
},
});

controller.updateSecurityAlertResponse(modifiedTransactionMeta.id, {
reason: 'NA',
// This is API specific hence naming convention is not followed.
// eslint-disable-next-line @typescript-eslint/naming-convention
result_type: BLOCKAID_RESULT_TYPE_MALICIOUS,
});

await flushPromises();

expect(getSimulationDataMock).toHaveBeenCalledTimes(1);
});

it('if first simulated transaction value threshold reached', async () => {
const modifiedSimulationMock = merge({}, SIMULATION_DATA_MOCK, {
nativeBalanceChange: {
difference: '0x64',
},
});

getSimulationDataMock.mockResolvedValueOnce(modifiedSimulationMock);
const { controller } = setupController();

// Transaction value = 0x5E => 94
await controller.addTransaction({
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
value: '0x5',
});

await flushPromises();

// First call for initial simulation
// Second call for re-simulation
expect(getSimulationDataMock).toHaveBeenCalledTimes(2);
expect(
getSimulationDataMock.mock.calls[1][1]?.isReSimulatedDueToSecurity,
).toBe(true);
});
});

describe('does not re-simulate', () => {
it('when security alert is not malicious', async () => {
const modifiedTransactionMeta = merge({}, TRANSACTION_META_MOCK, {
txParams: {
value: '0x1',
},
});

const modifiedSimulationData = merge({}, SIMULATION_DATA_MOCK, {
nativeBalanceChange: {
difference: '0x1',
},
});

getSimulationDataMock.mockResolvedValueOnce(modifiedSimulationData);

const { controller } = setupController({
options: {
state: {
transactions: [modifiedTransactionMeta],
},
},
});

controller.updateSecurityAlertResponse(modifiedTransactionMeta.id, {
reason: 'NA',
// This is API specific hence naming convention is not followed.
// eslint-disable-next-line @typescript-eslint/naming-convention
result_type: 'warning',
});

await flushPromises();

expect(getSimulationDataMock).toHaveBeenCalledTimes(0);
});

it('when transaction value is below the threshold', async () => {
const modifiedSimulationMock = merge({}, SIMULATION_DATA_MOCK, {
nativeBalanceChange: {
difference: '0x64',
},
});

getSimulationDataMock.mockResolvedValueOnce(modifiedSimulationMock);
const { controller } = setupController();

// Transaction value = 0x64 = 100
await controller.addTransaction({
from: ACCOUNT_MOCK,
to: ACCOUNT_MOCK,
value: '0x64',
});

await flushPromises();

// First call for initial simulation
expect(getSimulationDataMock).toHaveBeenCalledTimes(1);
});

it.each(['value', 'to', 'data'])(
'if txParams.%s changes',
async (param) => {
const transactionId = '1';
const baseTransaction = {
id: transactionId,
chainId: toHex(5),
status: TransactionStatus.unapproved as const,
time: 123456789,
txParams: {
data: 'originalData',
gas: '50000',
gasPrice: '1000000000',
from: ACCOUNT_MOCK,
to: ACCOUNT_2_MOCK,
value: '5000000000000000000',
},
};
const transactionMeta: TransactionMeta = {
...baseTransaction,
history: [{ ...baseTransaction }],
};

const { controller } = setupController({
options: {
state: {
transactions: [
{
...transactionMeta,
},
],
},
},
updateToInitialState: true,
});

await controller.updateEditableParams(transactionMeta.id, {
...transactionMeta.txParams,
[param]: ACCOUNT_2_MOCK,
});

await flushPromises();

expect(getSimulationDataMock).toHaveBeenCalledTimes(1);
},
);
});
});
Loading
Loading