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

chore(runway): cherry-pick fix: freeze during swap with approval #11209

Merged
merged 3 commits into from
Sep 13, 2024
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
47 changes: 25 additions & 22 deletions app/components/Nav/Main/RootRPCMethodsUI.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import { STX_NO_HASH_ERROR } from '../../../util/smart-transactions/smart-publis
import { getSmartTransactionMetricsProperties } from '../../../util/smart-transactions';
import { cloneDeep, isEqual } from 'lodash';
import { selectSwapsTransactions } from '../../../selectors/transactionController';
import { updateSwapsTransaction } from '../../../util/swaps/swaps-transactions';

///: BEGIN:ONLY_INCLUDE_IF(preinstalled-snaps,external-snaps)
import InstallSnapApproval from '../../Approvals/InstallSnapApproval';
Expand All @@ -86,15 +87,12 @@ export const useSwapConfirmedEvent = ({ trackSwaps }) => {
const [transactionMetaIdsForListening, setTransactionMetaIdsForListening] =
useState([]);

const addTransactionMetaIdForListening = useCallback(
(txMetaId) => {
setTransactionMetaIdsForListening([
...transactionMetaIdsForListening,
txMetaId,
]);
},
[transactionMetaIdsForListening],
);
const addTransactionMetaIdForListening = useCallback((txMetaId) => {
setTransactionMetaIdsForListening((transactionMetaIdsForListening) => [
...transactionMetaIdsForListening,
txMetaId,
]);
}, []);
const swapsTransactions = useSwapsTransactions();

useEffect(() => {
Expand Down Expand Up @@ -144,8 +142,8 @@ const RootRPCMethodsUI = (props) => {
try {
const { TransactionController, SmartTransactionsController } =
Engine.context;
const newSwapsTransactions = swapsTransactions;
const swapTransaction = newSwapsTransactions[transactionMeta.id];
const swapTransaction = swapsTransactions[transactionMeta.id];

const {
sentAt,
gasEstimate,
Expand Down Expand Up @@ -187,15 +185,6 @@ const RootRPCMethodsUI = (props) => {
ethBalance,
);

newSwapsTransactions[transactionMeta.id].gasUsed = receipt.gasUsed;
if (tokensReceived) {
newSwapsTransactions[transactionMeta.id].receivedDestinationAmount =
new BigNumber(tokensReceived, 16).toString(10);
}
TransactionController.update((state) => {
state.swapsTransactions = newSwapsTransactions;
});

const timeToMine = currentBlock.timestamp - sentAt;
const estimatedVsUsedGasRatio = `${new BigNumber(receipt.gasUsed)
.div(gasEstimate)
Expand All @@ -218,8 +207,20 @@ const RootRPCMethodsUI = (props) => {
...swapTransaction.analytics,
account_type: getAddressAccountType(transactionMeta.txParams.from),
};
delete newSwapsTransactions[transactionMeta.id].analytics;
delete newSwapsTransactions[transactionMeta.id].paramsForAnalytics;

updateSwapsTransaction(transactionMeta.id, (swapsTransaction) => {
swapsTransaction.gasUsed = receipt.gasUsed;

if (tokensReceived) {
swapsTransaction.receivedDestinationAmount = new BigNumber(
tokensReceived,
16,
).toString(10);
}

delete swapsTransaction.analytics;
delete swapsTransaction.paramsForAnalytics;
});

const smartTransactionMetricsProperties =
getSmartTransactionMetricsProperties(
Expand All @@ -237,6 +238,8 @@ const RootRPCMethodsUI = (props) => {
...smartTransactionMetricsProperties,
};

Logger.log('Swaps', 'Sending metrics event', event);

trackEvent(event, { sensitiveProperties: { ...parameters } });
} catch (e) {
Logger.error(e, MetaMetricsEvents.SWAP_TRACKING_FAILED);
Expand Down
91 changes: 43 additions & 48 deletions app/components/UI/Swaps/QuotesView.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,9 @@ import trackErrorAsAnalytics from '../../../util/metrics/TrackError/trackErrorAs
import { selectGasFeeEstimates } from '../../../selectors/confirmTransaction';
import { selectShouldUseSmartTransaction } from '../../../selectors/smartTransactionsController';
import { selectGasFeeControllerEstimateType } from '../../../selectors/gasFeeController';
import { addSwapsTransaction } from '../../../util/swaps/swaps-transactions';

const LOG_PREFIX = 'Swaps';
const POLLING_INTERVAL = 30000;
const SLIPPAGE_BUCKETS = {
MEDIUM: AppConstants.GAS_OPTIONS.MEDIUM,
Expand Down Expand Up @@ -792,19 +794,15 @@ function SwapsQuotesView({
]);

const updateSwapsTransactions = useCallback(
async (
transactionMeta,
approvalTransactionMetaId,
newSwapsTransactions,
) => {
const { TransactionController } = Engine.context;
async (transactionMeta, approvalTransactionMetaId) => {
const ethQuery = Engine.getGlobalEthQuery();
const blockNumber = await query(ethQuery, 'blockNumber', []);
const currentBlock = await query(ethQuery, 'getBlockByNumber', [
blockNumber,
false,
]);
newSwapsTransactions[transactionMeta.id] = {

addSwapsTransaction(transactionMeta.id, {
action: 'swap',
sourceToken: {
address: sourceToken.address,
Expand Down Expand Up @@ -851,9 +849,6 @@ function SwapsQuotesView({
ethAccountBalance: accounts[selectedAddress].balance,
approvalTransactionMetaId,
},
};
TransactionController.update((state) => {
state.swapsTransactions = newSwapsTransactions;
});
},
[
Expand Down Expand Up @@ -922,7 +917,7 @@ function SwapsQuotesView({
);

const handleSwapTransaction = useCallback(
async (newSwapsTransactions, approvalTransactionMetaId) => {
async (approvalTransactionMetaId) => {
if (!selectedQuote) {
return;
}
Expand All @@ -944,19 +939,23 @@ function SwapsQuotesView({
},
);

Logger.log(LOG_PREFIX, 'Added trade transaction', transactionMeta.id);

await result;

updateSwapsTransactions(
transactionMeta,
approvalTransactionMetaId,
newSwapsTransactions,
Logger.log(
LOG_PREFIX,
'Submitted trade transaction',
transactionMeta.id,
);

updateSwapsTransactions(transactionMeta, approvalTransactionMetaId);

setRecipient(selectedAddress);
await addTokenToAssetsController(destinationToken);
await addTokenToAssetsController(sourceToken);
} catch (e) {
// send analytics
Logger.log(LOG_PREFIX, 'Failed to submit trade transaction', e);
}
},
[
Expand All @@ -973,13 +972,8 @@ function SwapsQuotesView({
],
);

const handleApprovaltransaction = useCallback(
async (
TransactionController,
newSwapsTransactions,
approvalTransactionMetaId,
isHardwareAddress,
) => {
const handleApprovalTransaction = useCallback(
async (isHardwareAddress) => {
try {
resetTransaction();
const { transactionMeta, result } = await addTransaction(
Expand All @@ -996,11 +990,25 @@ function SwapsQuotesView({
},
);

Logger.log(
LOG_PREFIX,
'Added approval transaction',
transactionMeta.id,
);

await result;

Logger.log(
LOG_PREFIX,
'Submitted approval transaction',
transactionMeta.id,
);

setRecipient(selectedAddress);

approvalTransactionMetaId = transactionMeta.id;
newSwapsTransactions[transactionMeta.id] = {
const approvalTransactionMetaId = transactionMeta.id;

addSwapsTransaction(transactionMeta.id, {
action: 'approval',
sourceToken: {
address: sourceToken.address,
Expand All @@ -1011,27 +1019,25 @@ function SwapsQuotesView({
decodeApproveData(approvalTransaction.data).encodedAmount,
16,
).toString(10),
};
});

if (isHardwareAddress || shouldUseSmartTransaction) {
const { id: transactionId } = transactionMeta;

Engine.controllerMessenger.subscribeOnceIf(
'TransactionController:transactionConfirmed',
(transactionMeta) => {
if (transactionMeta.status === TransactionStatus.confirmed) {
handleSwapTransaction(
TransactionController,
newSwapsTransactions,
approvalTransactionMetaId,
isHardwareAddress,
);
handleSwapTransaction(approvalTransactionMetaId);
}
},
(transactionMeta) => transactionMeta.id === transactionId,
);
}

return approvalTransactionMetaId;
} catch (e) {
// send analytics
Logger.log(LOG_PREFIX, 'Failed to submit approval transaction', e);
}
},
[
Expand All @@ -1057,16 +1063,10 @@ function SwapsQuotesView({

startSwapAnalytics(selectedQuote, selectedAddress);

const { TransactionController } = Engine.context;

const newSwapsTransactions =
TransactionController.state.swapsTransactions || {};
let approvalTransactionMetaId;

if (approvalTransaction) {
await handleApprovaltransaction(
TransactionController,
newSwapsTransactions,
approvalTransactionMetaId,
approvalTransactionMetaId = await handleApprovalTransaction(
isHardwareAddress,
);

Expand All @@ -1080,12 +1080,7 @@ function SwapsQuotesView({
!shouldUseSmartTransaction ||
(shouldUseSmartTransaction && !approvalTransaction)
) {
await handleSwapTransaction(
TransactionController,
newSwapsTransactions,
approvalTransactionMetaId,
isHardwareAddress,
);
await handleSwapTransaction(approvalTransactionMetaId);
}

navigation.dangerouslyGetParent()?.pop();
Expand All @@ -1094,7 +1089,7 @@ function SwapsQuotesView({
selectedAddress,
approvalTransaction,
startSwapAnalytics,
handleApprovaltransaction,
handleApprovalTransaction,
handleSwapTransaction,
navigation,
shouldUseSmartTransaction,
Expand Down
1 change: 1 addition & 0 deletions app/core/Engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,7 @@ class Engine {
transactions: [],
lastFetchedBlockNumbers: {},
submitHistory: [],
swapsTransactions: {},
}));

LoggingController.clear();
Expand Down
2 changes: 1 addition & 1 deletion app/selectors/smartTransactionsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export const selectPendingSmartTransactionsBySender = (state: RootState) => {
// stx.uuid is one from sentinel API, not the same as tx.id which is generated client side
// Doesn't matter too much because we only care about the pending stx, confirmed txs are handled like normal
// However, this does make it impossible to read Swap data from TxController.swapsTransactions as that relies on client side tx.id
// To fix that we do transactionController.update({ swapsTransactions: newSwapsTransactions }) in app/util/smart-transactions/smart-tx.ts
// To fix that we create a duplicate swaps transaction for the stx.uuid in the smart publish hook.
id: stx.uuid,
status: stx.status?.startsWith(SmartTransactionStatuses.CANCELLED)
? SmartTransactionStatuses.CANCELLED
Expand Down
10 changes: 8 additions & 2 deletions app/util/smart-transactions/smart-publish-hook.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ jest.mock('uuid', () => ({
v1: jest.fn(() => 'approvalId'),
}));

jest.mock('../../core/Engine', () => ({
context: {
TransactionController: {
update: jest.fn(),
},
},
}));

const addressFrom = '0xabce7847fd3661a9b7c86aaf1daea08d9da5750e';
const transactionHash =
'0x0302b75dfb9fd9eb34056af031efcaee2a8cbd799ea054a85966165cd82a7356';
Expand Down Expand Up @@ -505,8 +513,6 @@ describe('submitSmartTransactionHook', () => {
isSwapTransaction: true,
},
});
//@ts-expect-error - We are calling a protected method for testing purposes
expect(request.transactionController.update).toHaveBeenCalledTimes(1);
});
});
});
14 changes: 7 additions & 7 deletions app/util/smart-transactions/smart-publish-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { v1 as random } from 'uuid';
import { decimalToHex } from '../conversions';
import { ApprovalTypes } from '../../core/RPCMethods/RPCMethodMiddleware';
import { RAMPS_SEND } from '../../components/UI/Ramp/constants';
import { addSwapsTransaction } from '../swaps/swaps-transactions';

export declare type Hex = `0x${string}`;

Expand Down Expand Up @@ -427,17 +428,16 @@ class SmartTransactionHook {
this.#approvalEnded = true;
};

#updateSwapsTransactions = (id: string) => {
#updateSwapsTransactions = (uuid: string) => {
// We do this so we can show the Swap data (e.g. ETH to USDC, fiat values) in the app/components/Views/TransactionsView/index.js
const newSwapsTransactions =
const swapsTransactions =
// @ts-expect-error This is not defined on the type, but is a field added in app/components/UI/Swaps/QuotesView.js
this.#transactionController.state.swapsTransactions || {};

newSwapsTransactions[id] = newSwapsTransactions[this.#transactionMeta.id];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.#transactionController as any).update((state: any) => {
state.swapsTransactions = newSwapsTransactions;
});
const originalSwapsTransaction =
swapsTransactions[this.#transactionMeta.id];

addSwapsTransaction(uuid, originalSwapsTransaction);
};
}

Expand Down
Loading
Loading