From 3b58d4b78ff047d7396268ff0b275436990878e9 Mon Sep 17 00:00:00 2001 From: Facundo Pezzola <22504074+fpezzola@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:47:05 -0300 Subject: [PATCH] [fix]: Allow users to edit gas limit on bridges (#331) --- .../src/controllers/BridgeController.ts | 6 +- .../transactions/GasPriceComponent.tsx | 74 +++++++++++++------ .../transactions/GasPriceSelector.tsx | 3 +- .../src/routes/bridge/BridgeConfirmPage.tsx | 10 ++- packages/ui/src/util/form.ts | 63 ++++++++++------ 5 files changed, 106 insertions(+), 50 deletions(-) diff --git a/packages/background/src/controllers/BridgeController.ts b/packages/background/src/controllers/BridgeController.ts index f900c5e20..fc78fb372 100644 --- a/packages/background/src/controllers/BridgeController.ts +++ b/packages/background/src/controllers/BridgeController.ts @@ -115,6 +115,7 @@ export interface BridgeTransaction extends BridgeParameters { customNonce?: number; flashbots?: boolean; gasPrice?: BigNumber; + gasLimit?: BigNumber; maxFeePerGas?: BigNumber; maxPriorityFeePerGas?: BigNumber; } @@ -590,6 +591,7 @@ export default class BridgeController extends BaseController< gasPrice, maxFeePerGas, maxPriorityFeePerGas, + gasLimit, params: { transactionRequest, fromToken, @@ -622,7 +624,9 @@ export default class BridgeController extends BaseController< maxFeePerGas: maxFeePerGas ? BigNumber.from(maxFeePerGas) : undefined, - gasLimit: BigNumber.from(transactionRequest.gasLimit), + gasLimit: BigNumber.from( + gasLimit ?? transactionRequest.gasLimit + ), nonce: customNonce, }, origin: 'blank', diff --git a/packages/ui/src/components/transactions/GasPriceComponent.tsx b/packages/ui/src/components/transactions/GasPriceComponent.tsx index d6e82f974..1091d39e5 100644 --- a/packages/ui/src/components/transactions/GasPriceComponent.tsx +++ b/packages/ui/src/components/transactions/GasPriceComponent.tsx @@ -1,4 +1,4 @@ -import { FunctionComponent, useRef, useState, useEffect } from "react" +import { FunctionComponent, useRef, useState, useEffect, useMemo } from "react" import classnames from "classnames" import { BigNumber } from "@ethersproject/bignumber" @@ -49,6 +49,7 @@ interface GasComponentProps { gasFees: TransactionFeeData selectedOption: GasPriceOption options: GasPriceOption[] + minGasLimit?: string setSelectedGas: (option: GasPriceOption) => void getGasOption: (label: string, gasFees: TransactionFeeData) => GasPriceOption } @@ -160,15 +161,21 @@ const GasSelectorBasic = (props: GasComponentProps) => { } // Schema -const schema = yup.object({ - gasLimit: makeStringNumberFormField("Gas limit is required", false), - maxPriorityFeePerGas: makeStringNumberFormField( - "Max tip is required", - true - ), - maxFeePerGas: makeStringNumberFormField("Max fee is required", false), -}) -type GasAdvancedForm = InferType +const schemaBuilder = ({ minGasLimit }: { minGasLimit?: string } = {}) => + yup.object({ + gasLimit: makeStringNumberFormField("Gas limit is required", false, { + min: [ + parseInt(minGasLimit ?? "0"), + `Gas limit can't be lower than ${parseInt(minGasLimit ?? "0")}`, + ], + }), + maxPriorityFeePerGas: makeStringNumberFormField( + "Max tip is required", + true + ), + maxFeePerGas: makeStringNumberFormField("Max fee is required", false), + }) +type GasAdvancedForm = InferType> // Advanced tab. Allows users to enter manual fee values. const GasSelectorAdvanced = (props: GasComponentProps) => { @@ -193,6 +200,14 @@ const GasSelectorAdvanced = (props: GasComponentProps) => { gasPricesLevels.average.maxPriorityFeePerGas ) + const schema = useMemo(() => { + return schemaBuilder({ minGasLimit: props.minGasLimit }) + }, [props.minGasLimit]) + + const [gasLimitWarning, setGasLimitWarning] = useState("") + const [tipWarning, setTipWarning] = useState("") + const [maxFeeWarning, setMaxFeeWarning] = useState("") + const { register, handleSubmit, @@ -236,16 +251,20 @@ const GasSelectorAdvanced = (props: GasComponentProps) => { setValue("maxFeePerGas", formatUnits(defaultFees.maxFeePerGas!, "gwei")) } + const validateGasLimit = (gasLimit?: BigNumber) => { + setGasLimitWarning( + gasLimit?.lt(defaultFees.gasLimit!) + ? `Gas limit lower than suggested (${defaultFees.gasLimit})` + : "" + ) + } + const validateFees = (fees: TransactionFeeData) => { clearErrors("maxFeePerGas") const baseFee = BigNumber.from(baseFeePerGas) - setGasLimitWarning( - fees.gasLimit?.lt(defaultFees.gasLimit!) - ? "Gas limit lower than suggested" - : "" - ) + validateGasLimit(fees.gasLimit) setMaxFeeWarning( fees.maxFeePerGas?.lt(baseFee.add(fees.maxPriorityFeePerGas!)) @@ -287,7 +306,6 @@ const GasSelectorAdvanced = (props: GasComponentProps) => { const handleBlur = () => { const values = getValues() - const fees: TransactionFeeData = { gasLimit: BigNumber.from( values.gasLimit === "" ? "0" : values.gasLimit @@ -321,10 +339,6 @@ const GasSelectorAdvanced = (props: GasComponentProps) => { setSelectedGas(custom) }) - const [gasLimitWarning, setGasLimitWarning] = useState("") - const [tipWarning, setTipWarning] = useState("") - const [maxFeeWarning, setMaxFeeWarning] = useState("") - return (
@@ -334,7 +348,9 @@ const GasSelectorAdvanced = (props: GasComponentProps) => { { autoComplete="off" onKeyDown={handleKeyDown} onInput={handleChangeAmountWei((value) => { + //If there was a warning, then check whether we need to clean it or not. + //If there weren't, lets validate that onBlur to avoid showing and clearing error everytime. + if (gasLimitWarning) { + validateGasLimit(BigNumber.from(value ?? "0")) + } setValue("gasLimit", value, { shouldValidate: true, }) @@ -388,7 +409,9 @@ const GasSelectorAdvanced = (props: GasComponentProps) => { { = ({ defaultGas, setGas, @@ -545,6 +571,7 @@ const GasPriceComponent: FunctionComponent<{ disabled, showEstimationError, displayOnlyMaxValue = false, + minGasLimit, }) => { //Popup variables const ref = useRef(null) @@ -894,6 +921,7 @@ const GasPriceComponent: FunctionComponent<{ setShowEstimationWarning(false) setActive(false) }} + minGasLimit={minGasLimit} getGasOption={getGasOption} />
diff --git a/packages/ui/src/components/transactions/GasPriceSelector.tsx b/packages/ui/src/components/transactions/GasPriceSelector.tsx index 481202295..5ce7efc8f 100644 --- a/packages/ui/src/components/transactions/GasPriceSelector.tsx +++ b/packages/ui/src/components/transactions/GasPriceSelector.tsx @@ -71,6 +71,7 @@ interface GasTabProps { selectedGasPrice: TransactionSpeedOption defaultGasLimit: BigNumber defaultGasPrice: BigNumber + minGasLimit?: number setUserChanged: (userChanged: boolean) => void handlePriceSelection: (price: TransactionSpeedOption) => void getSpeedOption: ( @@ -498,7 +499,7 @@ export const GasPriceSelector = (props: GasPriceSelectorProps) => { const [userChanged, setUserChanged] = useState(false) // Tabs variables - // if network is configured to not show gas levels or received gas does not match with average, default is advanced tab. + // if network is configured to noxt show gas levels or received gas does not match with average, default is advanced tab. const [tab, setTab] = useState( tabs[!showGasLevels || !defaultLevel ? 1 : 0] ) diff --git a/packages/ui/src/routes/bridge/BridgeConfirmPage.tsx b/packages/ui/src/routes/bridge/BridgeConfirmPage.tsx index 5c479100f..8063b5bd9 100644 --- a/packages/ui/src/routes/bridge/BridgeConfirmPage.tsx +++ b/packages/ui/src/routes/bridge/BridgeConfirmPage.tsx @@ -1,9 +1,6 @@ import AssetAmountDisplay from "../../components/assets/AssetAmountDisplay" -import ClickableText from "../../components/button/ClickableText" -import Divider from "../../components/Divider" import GasPriceComponent from "../../components/transactions/GasPriceComponent" import HardwareDeviceNotLinkedDialog from "../../components/dialog/HardwareDeviceNotLinkedDialog" -import LoadingDialog from "../../components/dialog/LoadingDialog" import NetworkDisplay from "../../components/network/NetworkDisplay" import PopupFooter from "../../components/popup/PopupFooter" import PopupHeader from "../../components/popup/PopupHeader" @@ -40,7 +37,6 @@ import { isHardwareWallet } from "../../util/account" import { useBlankState } from "../../context/background/backgroundHooks" import { useGasPriceData } from "../../context/hooks/useGasPriceData" import { useHasSufficientBalance } from "../../context/hooks/useHasSufficientBalance" -import { useInProgressAllowanceTransaction } from "../../context/hooks/useInProgressAllowanceTransaction" import { useInProgressInternalTransaction } from "../../context/hooks/useInProgressInternalTransaction" import { useUserSettings } from "../../context/hooks/useUserSettings" import { useLocationRecovery } from "../../util/hooks/useLocationRecovery" @@ -354,6 +350,7 @@ const BridgeConfirmPage: FunctionComponent<{}> = () => { maxFeePerGas: isEIP1559Compatible ? selectedFees.maxFeePerGas : undefined, + gasLimit: selectedGasLimit, } await executeBridge(txParams) @@ -661,6 +658,11 @@ const BridgeConfirmPage: FunctionComponent<{}> = () => { }} isParentLoading={isGasLoading} disabled={isGasLoading} + minGasLimit={ + transactionRequest + ? transactionRequest.gasLimit + : undefined + } displayOnlyMaxValue /> ) : ( diff --git a/packages/ui/src/util/form.ts b/packages/ui/src/util/form.ts index 5033fb0f4..23229962e 100644 --- a/packages/ui/src/util/form.ts +++ b/packages/ui/src/util/form.ts @@ -1,5 +1,10 @@ import * as yup from "yup" +type ValidationInput = [T, string] + +type StringNumberValidator = { + min?: ValidationInput +} /** * This function create a yup schema field when you need a number (most of the time gas fees) in a form. * If you need to add more verification you can do it like so: @@ -11,9 +16,10 @@ import * as yup from "yup" */ export const makeStringNumberFormField = ( requiredMessage: string, - isGteThan0: boolean = true -) => - yup + isGteThan0: boolean = true, + { min }: StringNumberValidator = {} +) => { + let schema = yup .string() .required(requiredMessage) .test("is-correct", "Please enter a number.", (value) => { @@ -30,6 +36,21 @@ export const makeStringNumberFormField = ( return isGteThan0 ? parseFloat(value) >= 0 : parseFloat(value) > 0 }) + if (min) { + const [minValue, message] = min + schema = schema.test("is-min", message, (value) => { + if ( + typeof value !== "string" && + typeof minValue !== "undefined" && + value + ) { + return false + } + return parseFloat(value ?? "0") >= minValue + }) + } + return schema +} /** * This function check if the input is a valid number * @param e Keyboard event @@ -47,29 +68,29 @@ export const handleKeyDown = (e: React.KeyboardEvent) => { } } -const makeHandleChangeAmount = (replace: RegExp) => ( - cb: (value: string) => void, - defaultValue: string = "" -) => (event: React.ChangeEvent) => { - let value: string = event.target.value +const makeHandleChangeAmount = + (replace: RegExp) => + (cb: (value: string) => void, defaultValue: string = "") => + (event: React.ChangeEvent) => { + let value: string = event.target.value - value = value - .replace(",", ".") - .replace(replace, "") - .replace(/(\..*?)\..*/g, "$1") + value = value + .replace(",", ".") + .replace(replace, "") + .replace(/(\..*?)\..*/g, "$1") - const match = value.match(/[0-9]+[.,]([0-9]+)/) + const match = value.match(/[0-9]+[.,]([0-9]+)/) - if (match && match[1].length > 9) { - value = value.substring(0, value.length - 1) - } + if (match && match[1].length > 9) { + value = value.substring(0, value.length - 1) + } - if (!value || value === ".") { - value = defaultValue - } + if (!value || value === ".") { + value = defaultValue + } - cb(value) -} + cb(value) + } export const handleChangeAmountGwei = makeHandleChangeAmount(/[^0-9.]/g)