From 758fc3c0c06a6414bb6516bd936de0ea95d89a52 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Thu, 28 Jan 2021 05:12:02 -0300 Subject: [PATCH] (Fix) Estimate gas for wallet connect (#1806) * Guard for empty result * Type TextField * Fix warning of InputAdornment in SendFunds modal * Re-enable gas estimation for wallet connect * Replace web3.call on parseRequiredTxGasResponse with axios post to infura * Adds estimateGasWithInfura and estimateGasWithWeb3Provider for changing the estimation method if we are in a non-infura-supported network * Revert calculateMinimumGasForTransaction change to leave the change for the already-open pr * Renames estimateGasWithInfura with estimateGasWithRPCCall Replaces web3 with web3ReadOnly in estimateGasWithRPCCall Co-authored-by: Mati Dastugue Co-authored-by: Fernando Co-authored-by: Daniel Sanchez --- src/components/TransactionsFees/index.tsx | 11 -- src/components/forms/TextField/index.tsx | 144 +++++++++++------- src/config/index.ts | 13 +- src/logic/hooks/useEstimateTransactionGas.tsx | 5 - src/logic/safe/transactions/gas.ts | 58 ++++++- .../SendModal/screens/SendFunds/index.tsx | 10 +- 6 files changed, 151 insertions(+), 90 deletions(-) diff --git a/src/components/TransactionsFees/index.tsx b/src/components/TransactionsFees/index.tsx index 372c8b52..c5e54f19 100644 --- a/src/components/TransactionsFees/index.tsx +++ b/src/components/TransactionsFees/index.tsx @@ -3,10 +3,6 @@ import { EstimationStatus } from 'src/logic/hooks/useEstimateTransactionGas' import Paragraph from 'src/components/layout/Paragraph' import { getNetworkInfo } from 'src/config' import { TransactionFailText } from 'src/components/TransactionFailText' -import { useSelector } from 'react-redux' -import { providerNameSelector } from 'src/logic/wallets/store/selectors' -import { sameString } from 'src/utils/strings' -import { WALLETS } from 'src/config/networks/network.d' type TransactionFailTextProps = { txEstimationExecutionStatus: EstimationStatus @@ -24,8 +20,6 @@ export const TransactionFees = ({ isOffChainSignature, txEstimationExecutionStatus, }: TransactionFailTextProps): React.ReactElement | null => { - const providerName = useSelector(providerNameSelector) - let transactionAction if (isCreation) { transactionAction = 'create' @@ -35,11 +29,6 @@ export const TransactionFees = ({ transactionAction = 'approve' } - // FIXME this should be removed when estimating with WalletConnect correctly - if (!providerName || sameString(providerName, WALLETS.WALLET_CONNECT)) { - return null - } - return ( <> diff --git a/src/components/forms/TextField/index.tsx b/src/components/forms/TextField/index.tsx index c1998d63..19123e33 100644 --- a/src/components/forms/TextField/index.tsx +++ b/src/components/forms/TextField/index.tsx @@ -1,5 +1,5 @@ import MuiTextField from '@material-ui/core/TextField' -import { withStyles } from '@material-ui/core/styles' +import { createStyles, makeStyles } from '@material-ui/core/styles' import React from 'react' import { lg } from 'src/theme/variables' @@ -10,65 +10,93 @@ const overflowStyle = { width: '100%', } -const styles = () => ({ - root: { - paddingTop: lg, - paddingBottom: '12px', - lineHeight: 0, - }, -}) +const styles = () => + createStyles({ + root: { + paddingTop: lg, + paddingBottom: '12px', + lineHeight: 0, + }, + }) -class TextField extends React.PureComponent { - render() { - const { - classes, - input: { name, onChange, value, ...restInput }, - inputAdornment, - meta, - multiline, - rows, - testId, - text, - ...rest - } = this.props - const helperText = value ? text : undefined - const showError = (meta.touched || !meta.pristine) && !meta.valid - const hasError = !!meta.error || (!meta.modifiedSinceLastSubmit && !!meta.submitError) - const errorMessage = meta.error || meta.submitError - const isInactiveAndPristineOrUntouched = !meta.active && (meta.pristine || !meta.touched) - const isInvalidAndUntouched = typeof meta.error === 'undefined' ? true : !meta.touched +const useStyles = makeStyles(styles) - const disableUnderline = isInactiveAndPristineOrUntouched && isInvalidAndUntouched - - const inputRoot = helperText ? classes.root : '' - const statusClasses = meta.valid ? 'isValid' : hasError && showError ? 'isInvalid' : '' - const inputProps = { - ...restInput, - autoComplete: 'off', - 'data-testid': testId, - } - const inputRootProps = { - ...inputAdornment, - className: `${inputRoot} ${statusClasses}`, - disableUnderline: disableUnderline, - } - - return ( - - ) +type Props = { + input: { + name: string + onChange?: () => void + value: string + placeholder: string + type: string } + meta: { + touched?: boolean + pristine?: boolean + valid?: boolean + error?: string + modifiedSinceLastSubmit?: boolean + submitError?: boolean + active?: boolean + } + inputAdornment?: { endAdornment: React.ReactElement } | undefined + multiline: boolean + rows?: string + testId: string + text: string + disabled?: boolean + rowsMax?: number + className?: string } -export default withStyles(styles as any)(TextField) +const TextField = (props: Props): React.ReactElement => { + const { + input: { name, onChange, value, ...restInput }, + inputAdornment, + meta, + multiline, + rows, + testId, + text, + ...rest + } = props + const classes = useStyles() + const helperText = value ? text : undefined + const showError = (meta.touched || !meta.pristine) && !meta.valid + const hasError = !!meta.error || (!meta.modifiedSinceLastSubmit && !!meta.submitError) + const errorMessage = meta.error || meta.submitError + const isInactiveAndPristineOrUntouched = !meta.active && (meta.pristine || !meta.touched) + const isInvalidAndUntouched = typeof meta.error === 'undefined' ? true : !meta.touched + + const disableUnderline = isInactiveAndPristineOrUntouched && isInvalidAndUntouched + + const inputRoot = helperText ? classes.root : '' + const statusClasses = meta.valid ? 'isValid' : hasError && showError ? 'isInvalid' : '' + const inputProps = { + ...restInput, + autoComplete: 'off', + 'data-testid': testId, + } + const inputRootProps = { + ...inputAdornment, + className: `${inputRoot} ${statusClasses}`, + disableUnderline: disableUnderline, + } + + return ( + + ) +} + +export default TextField diff --git a/src/config/index.ts b/src/config/index.ts index d8660603..e1affc98 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -17,6 +17,8 @@ export const getNetworkId = (): ETHEREUM_NETWORK => ETHEREUM_NETWORK[NETWORK] export const getNetworkName = (): string => ETHEREUM_NETWORK[getNetworkId()] +export const usesInfuraRPC = [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY].includes(getNetworkId()) + const getCurrentEnvironment = (): string => { switch (NODE_ENV) { case 'test': { @@ -76,15 +78,8 @@ export const getGasPrice = (): number | undefined => getConfig()?.gasPrice export const getGasPriceOracle = (): GasPriceOracle | undefined => getConfig()?.gasPriceOracle -export const getRpcServiceUrl = (): string => { - const usesInfuraRPC = [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY].includes(getNetworkId()) - - if (usesInfuraRPC) { - return `${getConfig().rpcServiceUrl}/${INFURA_TOKEN}` - } - - return getConfig().rpcServiceUrl -} +export const getRpcServiceUrl = (): string => + usesInfuraRPC ? `${getConfig().rpcServiceUrl}/${INFURA_TOKEN}` : getConfig().rpcServiceUrl export const getSafeServiceBaseUrl = (safeAddress: string) => `${getTxServiceUrl()}/safes/${safeAddress}` diff --git a/src/logic/hooks/useEstimateTransactionGas.tsx b/src/logic/hooks/useEstimateTransactionGas.tsx index fa5cc94e..77bce100 100644 --- a/src/logic/hooks/useEstimateTransactionGas.tsx +++ b/src/logic/hooks/useEstimateTransactionGas.tsx @@ -22,7 +22,6 @@ import { Confirmation } from 'src/logic/safe/store/models/types/confirmation' import { checkIfOffChainSignatureIsPossible } from 'src/logic/safe/safeTxSigner' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { sameString } from 'src/utils/strings' -import { WALLETS } from 'src/config/networks/network.d' export enum EstimationStatus { LOADING = 'LOADING', @@ -177,10 +176,6 @@ export const useEstimateTransactionGas = ({ if (!txData.length) { return } - // FIXME this should be removed when estimating with WalletConnect correctly - if (!providerName || sameString(providerName, WALLETS.WALLET_CONNECT)) { - return null - } const isExecution = checkIfTxIsExecution(Number(threshold), preApprovingOwner, txConfirmations?.size, txType) const isCreation = checkIfTxIsCreation(txConfirmations?.size || 0, txType) diff --git a/src/logic/safe/transactions/gas.ts b/src/logic/safe/transactions/gas.ts index e808860c..3edb1b90 100644 --- a/src/logic/safe/transactions/gas.ts +++ b/src/logic/safe/transactions/gas.ts @@ -1,12 +1,14 @@ import { BigNumber } from 'bignumber.js' import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts' import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions' -import { getWeb3 } from 'src/logic/wallets/getWeb3' -import { sameString } from 'src/utils/strings' +import { getWeb3, web3ReadOnly } from 'src/logic/wallets/getWeb3' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner' import { List } from 'immutable' import { Confirmation } from 'src/logic/safe/store/models/types/confirmation' +import axios from 'axios' +import { getRpcServiceUrl, usesInfuraRPC } from 'src/config' +import { sameString } from 'src/utils/strings' // Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount const parseRequiredTxGasResponse = (data: string): number => { @@ -88,7 +90,7 @@ export const getDataFromNodeErrorMessage = (errorMessage: string): string | unde } } -export const getGasEstimationTxResponse = async (txConfig: { +const estimateGasWithWeb3Provider = async (txConfig: { to: string from: string data: string @@ -116,12 +118,56 @@ export const getGasEstimationTxResponse = async (txConfig: { return new BigNumber(estimationData.substring(138), 16).toNumber() } - - // This will fail in case that we receive an EMPTY_DATA on the GETH node gas estimation (for version < v1.9.24 of geth nodes) - // We cannot throw this error above because it will be captured again on the catch block bellow throw new Error('Error while estimating the gas required for tx') } +const estimateGasWithRPCCall = async (txConfig: { + to: string + from: string + data: string + gasPrice?: number + gas?: number +}): Promise => { + try { + const { data } = await axios.post(getRpcServiceUrl(), { + jsonrpc: '2.0', + method: 'eth_call', + id: 1, + params: [ + { + ...txConfig, + gasPrice: web3ReadOnly.utils.toHex(txConfig.gasPrice || 0), + gas: txConfig.gas ? web3ReadOnly.utils.toHex(txConfig.gas) : undefined, + }, + 'latest', + ], + }) + + const { error } = data + if (error?.data) { + return new BigNumber(data.error.data.substring(138), 16).toNumber() + } + } catch (error) { + console.log('Gas estimation endpoint errored: ', error.message) + } + throw new Error('Error while estimating the gas required for tx') +} + +export const getGasEstimationTxResponse = async (txConfig: { + to: string + from: string + data: string + gasPrice?: number + gas?: number +}): Promise => { + // If we are in a infura supported network we estimate using infura + if (usesInfuraRPC) { + return estimateGasWithRPCCall(txConfig) + } + // Otherwise we estimate using the current connected provider + return estimateGasWithWeb3Provider(txConfig) +} + const calculateMinimumGasForTransaction = async ( additionalGasBatches: number[], safeAddress: string, diff --git a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx index f8b3c7ce..f39e9ab2 100644 --- a/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx +++ b/src/routes/safe/components/Balances/SendModal/screens/SendFunds/index.tsx @@ -75,6 +75,10 @@ type SendFundsProps = { amount?: string } +const InputAdornmentChildSymbol = ({ symbol }: { symbol?: string }): ReactElement => { + return <>{symbol} +} + const SendFunds = ({ onClose, onNext, recipientAddress, selectedToken = '', amount }: SendFundsProps): ReactElement => { const classes = useStyles() const tokens = useSelector(extendedSafeTokensSelector) @@ -295,7 +299,11 @@ const SendFunds = ({ onClose, onNext, recipientAddress, selectedToken = '', amou {selectedToken?.symbol}, + endAdornment: ( + + + + ), }} name="amount" placeholder="Amount*"