diff --git a/shared/lib/swaps-utils.js b/shared/lib/swaps-utils.js index f27f4c9e8a8f..62c71ecbaadb 100644 --- a/shared/lib/swaps-utils.js +++ b/shared/lib/swaps-utils.js @@ -18,6 +18,8 @@ import { addHexPrefix } from '../../app/scripts/lib/util'; import { decimalToHex } from '../modules/conversion.utils'; import fetchWithCache from './fetch-with-cache'; +const FALLBACK_GAS_MULTIPLIER = 1.5; + const TEST_CHAIN_IDS = [CHAIN_IDS.GOERLI, CHAIN_IDS.LOCALHOST]; const clientIdHeader = { 'X-Client-Id': SWAPS_CLIENT_ID }; @@ -325,3 +327,37 @@ export async function fetchTradesInfo( return newQuotes; } + +/** + * Given a gas estimate, gas multiplier, max gas, and custom max gas, returns the max gas limit + * to use for a transaction. + * + * @param {string} gasEstimate - The gas estimate for the transaction. + * @param {number} gasMultiplier - The gas multiplier to use. + * @param {number} maxGas - The max gas limit to use. + * @param {string} customMaxGas - The custom max gas limit to use. + * @returns {string} The max gas limit to use for the transaction. + */ + +export function calculateMaxGasLimit( + gasEstimate, + gasMultiplier = FALLBACK_GAS_MULTIPLIER, + maxGas, + customMaxGas, +) { + const gasLimitForMax = new BigNumber(gasEstimate || 0, 16) + .round(0) + .toString(16); + + const usedGasLimitWithMultiplier = new BigNumber(gasLimitForMax, 16) + .times(gasMultiplier, 10) + .round(0) + .toString(16); + + const nonCustomMaxGasLimit = gasEstimate + ? usedGasLimitWithMultiplier + : `0x${decimalToHex(maxGas || 0)}`; + const maxGasLimit = customMaxGas || nonCustomMaxGasLimit; + + return maxGasLimit; +} diff --git a/shared/lib/swaps-utils.test.js b/shared/lib/swaps-utils.test.js index 5db5fb23085b..4cbc0927e1f3 100644 --- a/shared/lib/swaps-utils.test.js +++ b/shared/lib/swaps-utils.test.js @@ -10,7 +10,11 @@ import { TOKENS, MOCK_TRADE_RESPONSE_2, } from '../../ui/pages/swaps/swaps-util-test-constants'; -import { fetchTradesInfo, shouldEnableDirectWrapping } from './swaps-utils'; +import { + fetchTradesInfo, + shouldEnableDirectWrapping, + calculateMaxGasLimit, +} from './swaps-utils'; jest.mock('./storage-helpers', () => ({ getStorageItem: jest.fn(), @@ -224,4 +228,46 @@ describe('Swaps Utils', () => { expect(shouldEnableDirectWrapping(CHAIN_IDS.MAINNET)).toBe(false); }); }); + + describe('calculateMaxGasLimit', () => { + const gasEstimate = '0x37b15'; + const maxGas = 273740; + let expectedMaxGas = '42d4c'; + let gasMultiplier = 1.2; + let customMaxGas = ''; + + it('should return the max gas limit', () => { + const result = calculateMaxGasLimit( + gasEstimate, + gasMultiplier, + maxGas, + customMaxGas, + ); + expect(result).toStrictEqual(expectedMaxGas); + }); + + it('should return the custom max gas limit', () => { + customMaxGas = '46d4c'; + const result = calculateMaxGasLimit( + gasEstimate, + gasMultiplier, + maxGas, + customMaxGas, + ); + expect(result).toStrictEqual(customMaxGas); + }); + + it('should return the max gas limit with a gas multiplier of 4.5', () => { + gasMultiplier = 4.5; + expectedMaxGas = 'fa9df'; + customMaxGas = ''; + const result = calculateMaxGasLimit( + gasEstimate, + gasMultiplier, + maxGas, + customMaxGas, + ); + expect(result).toStrictEqual(expectedMaxGas); + }); + }); }); diff --git a/ui/ducks/swaps/swaps.js b/ui/ducks/swaps/swaps.js index 56a7273e8d5b..59fadda433d5 100644 --- a/ui/ducks/swaps/swaps.js +++ b/ui/ducks/swaps/swaps.js @@ -94,6 +94,7 @@ import { } from '../../../shared/lib/transactions-controller-utils'; import { EtherDenomination } from '../../../shared/constants/common'; import { Numeric } from '../../../shared/modules/Numeric'; +import { calculateMaxGasLimit } from '../../../shared/lib/swaps-utils'; export const GAS_PRICES_LOADING_STATES = { INITIAL: 'INITIAL', @@ -102,8 +103,6 @@ export const GAS_PRICES_LOADING_STATES = { COMPLETED: 'COMPLETED', }; -export const FALLBACK_GAS_MULTIPLIER = 1.5; - const initialState = { aggregatorMetadata: null, approveTxId: null, @@ -1106,20 +1105,16 @@ export const signAndSendTransactions = ( const usedQuote = getUsedQuote(state); const usedTradeTxParams = usedQuote.trade; - const estimatedGasLimit = new BigNumber( - usedQuote?.gasEstimate || 0, - 16, - ).toString(16); - - const maxGasLimit = - customSwapsGas || - (usedQuote?.gasEstimate - ? `0x${estimatedGasLimit}` - : `0x${decimalToHex( - new BigNumber(usedQuote?.maxGas) - .mul(usedQuote?.gasMultiplier || FALLBACK_GAS_MULTIPLIER) - .toString() || 0, - )}`); + const estimatedGasLimit = new BigNumber(usedQuote?.gasEstimate || 0, 16) + .round(0) + .toString(16); + + const maxGasLimit = calculateMaxGasLimit( + usedQuote?.gasEstimate, + usedQuote?.gasMultiplier, + usedQuote?.maxGas, + customSwapsGas, + ); const usedGasPrice = getUsedSwapsGasPrice(state); usedTradeTxParams.gas = maxGasLimit; diff --git a/ui/pages/swaps/prepare-swap-page/review-quote.js b/ui/pages/swaps/prepare-swap-page/review-quote.js index 011ab04bd8d9..5fb2badd8bcb 100644 --- a/ui/pages/swaps/prepare-swap-page/review-quote.js +++ b/ui/pages/swaps/prepare-swap-page/review-quote.js @@ -22,7 +22,6 @@ import { usePrevious } from '../../../hooks/usePrevious'; import { useGasFeeInputs } from '../../confirmations/hooks/useGasFeeInputs'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import { - FALLBACK_GAS_MULTIPLIER, getQuotes, getSelectedQuote, getApproveTxParams, @@ -135,7 +134,10 @@ import { toPrecisionWithoutTrailingZeros, } from '../../../../shared/lib/transactions-controller-utils'; import { addHexPrefix } from '../../../../app/scripts/lib/util'; -import { calcTokenValue } from '../../../../shared/lib/swaps-utils'; +import { + calcTokenValue, + calculateMaxGasLimit, +} from '../../../../shared/lib/swaps-utils'; import { GAS_FEES_LEARN_MORE_URL } from '../../../../shared/lib/ui-utils'; import ExchangeRateDisplay from '../exchange-rate-display'; import InfoTooltip from '../../../components/ui/info-tooltip'; @@ -295,20 +297,12 @@ export default function ReviewQuote({ setReceiveToAmount }) { usedQuote?.gasEstimateWithRefund || `0x${decimalToHex(usedQuote?.averageGas || 0)}`; - const estimatedGasLimit = new BigNumber( - usedQuote?.gasEstimate || 0, - 16, - ).toString(16); - - const nonCustomMaxGasLimit = usedQuote?.gasEstimate - ? `0x${estimatedGasLimit}` - : `0x${decimalToHex( - new BigNumber(usedQuote?.maxGas) - .mul(usedQuote?.gasMultiplier || FALLBACK_GAS_MULTIPLIER) - .toString() || 0, - )}`; - - const maxGasLimit = customMaxGas || nonCustomMaxGasLimit; + const maxGasLimit = calculateMaxGasLimit( + usedQuote?.gasEstimate, + usedQuote?.gasMultiplier, + usedQuote?.maxGas, + customMaxGas, + ); let maxFeePerGas; let maxPriorityFeePerGas; diff --git a/ui/pages/swaps/view-quote/view-quote.js b/ui/pages/swaps/view-quote/view-quote.js index 3525cb457f94..f67f727317a1 100644 --- a/ui/pages/swaps/view-quote/view-quote.js +++ b/ui/pages/swaps/view-quote/view-quote.js @@ -22,7 +22,6 @@ import { useGasFeeInputs } from '../../confirmations/hooks/useGasFeeInputs'; import { MetaMetricsContext } from '../../../contexts/metametrics'; import FeeCard from '../fee-card'; import { - FALLBACK_GAS_MULTIPLIER, getQuotes, getSelectedQuote, getApproveTxParams, @@ -109,7 +108,10 @@ import { toPrecisionWithoutTrailingZeros, } from '../../../../shared/lib/transactions-controller-utils'; import { addHexPrefix } from '../../../../app/scripts/lib/util'; -import { calcTokenValue } from '../../../../shared/lib/swaps-utils'; +import { + calcTokenValue, + calculateMaxGasLimit, +} from '../../../../shared/lib/swaps-utils'; import { addHexes, decGWEIToHexWEI, @@ -230,17 +232,12 @@ export default function ViewQuote() { usedQuote?.gasEstimateWithRefund || `0x${decimalToHex(usedQuote?.averageGas || 0)}`; - const gasLimitForMax = usedQuote?.gasEstimate || `0x0`; - - const usedGasLimitWithMultiplier = new BigNumber(gasLimitForMax, 16) - .times(usedQuote?.gasMultiplier || FALLBACK_GAS_MULTIPLIER, 10) - .round(0) - .toString(16); - - const nonCustomMaxGasLimit = usedQuote?.gasEstimate - ? usedGasLimitWithMultiplier - : `0x${decimalToHex(usedQuote?.maxGas || 0)}`; - const maxGasLimit = customMaxGas || nonCustomMaxGasLimit; + const maxGasLimit = calculateMaxGasLimit( + usedQuote?.gasEstimate, + usedQuote?.gasMultiplier, + usedQuote?.maxGas, + customMaxGas, + ); let maxFeePerGas; let maxPriorityFeePerGas;