Fix ignored safeTxGas from app (#2193)
This commit is contained in:
parent
44074475d7
commit
e3797d9e78
|
@ -157,7 +157,6 @@ export const useEstimateTransactionGas = ({
|
|||
txRecipient,
|
||||
txAmount: txAmount || '0',
|
||||
operation: operation || CALL,
|
||||
safeTxGas,
|
||||
})
|
||||
}
|
||||
if (isExecution || approvalAndExecution) {
|
||||
|
|
Binary file not shown.
|
@ -1,158 +1,12 @@
|
|||
import axios from 'axios'
|
||||
import { BigNumber } from 'bignumber.js'
|
||||
import { List } from 'immutable'
|
||||
|
||||
import { getRpcServiceUrl, usesInfuraRPC } from 'src/config'
|
||||
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
|
||||
import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
||||
import { getWeb3, web3ReadOnly } from 'src/logic/wallets/getWeb3'
|
||||
import { calculateGasOf } from 'src/logic/wallets/ethTransactions'
|
||||
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { generateSignaturesFromTxConfirmations } from 'src/logic/safe/safeTxSigner'
|
||||
import { fetchSafeTxGasEstimation } from 'src/logic/safe/api/fetchSafeTxGasEstimation'
|
||||
import { Confirmation } from 'src/logic/safe/store/models/types/confirmation'
|
||||
import { checksumAddress } from 'src/utils/checksumAddress'
|
||||
import { sameString } from 'src/utils/strings'
|
||||
|
||||
interface ErrorDataJson extends JSON {
|
||||
originalError?: {
|
||||
data?: string
|
||||
}
|
||||
data?: string
|
||||
}
|
||||
|
||||
const getJSONOrNullFromString = (stringInput: string): ErrorDataJson | null => {
|
||||
try {
|
||||
return JSON.parse(stringInput)
|
||||
} catch (error) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Parses the result from the error message (GETH, OpenEthereum/Parity and Nethermind) and returns the data value
|
||||
export const getDataFromNodeErrorMessage = (errorMessage: string): string | undefined => {
|
||||
// Replace illegal characters that often comes within the error string (like <20> for example)
|
||||
// https://stackoverflow.com/questions/12754256/removing-invalid-characters-in-javascript
|
||||
const normalizedErrorString = errorMessage.replace(/\uFFFD/g, '')
|
||||
|
||||
// Extracts JSON object from the error message
|
||||
const [, ...error] = normalizedErrorString.split('\n')
|
||||
|
||||
try {
|
||||
const errorAsString = error.join('')
|
||||
const errorAsJSON = getJSONOrNullFromString(errorAsString)
|
||||
|
||||
// Trezor wallet returns the error not as an JSON object but directly as string
|
||||
if (!errorAsJSON) {
|
||||
return errorAsString.length ? errorAsString : undefined
|
||||
}
|
||||
|
||||
// For new GETH nodes they will return the data as error in the format:
|
||||
// {
|
||||
// "originalError": {
|
||||
// "code": number,
|
||||
// "data": string,
|
||||
// "message": "execution reverted: ..."
|
||||
// }
|
||||
// }
|
||||
if (errorAsJSON.originalError && errorAsJSON.originalError.data) {
|
||||
return errorAsJSON.originalError.data
|
||||
}
|
||||
|
||||
// OpenEthereum/Parity nodes will return the data as error in the format:
|
||||
// {
|
||||
// "error": {
|
||||
// "code": number,
|
||||
// "message": string,
|
||||
// "data": "revert: 0x..." -> this is the result data that should be extracted from the message
|
||||
// },
|
||||
// "id": number
|
||||
// }
|
||||
if (errorAsJSON?.data) {
|
||||
const [, dataResult] = errorAsJSON.data.split(' ')
|
||||
return dataResult
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error trying to extract data from node error message: ${errorMessage}`)
|
||||
}
|
||||
}
|
||||
|
||||
const estimateGasWithWeb3Provider = async (txConfig: {
|
||||
to: string
|
||||
from: string
|
||||
data: string
|
||||
gasPrice?: number
|
||||
gas?: number
|
||||
}): Promise<number> => {
|
||||
const web3 = getWeb3()
|
||||
try {
|
||||
const result = await web3.eth.call(txConfig)
|
||||
|
||||
// GETH Nodes (geth version < v1.9.24)
|
||||
// In case that the gas is not enough we will receive an EMPTY data
|
||||
// Otherwise we will receive the gas amount as hash data -> this is valid for old versions of GETH nodes ( < v1.9.24)
|
||||
|
||||
if (!sameString(result, EMPTY_DATA)) {
|
||||
return new BigNumber(result.substring(138), 16).toNumber()
|
||||
}
|
||||
} catch (error) {
|
||||
// So we try to extract the estimation result within the error in case is possible
|
||||
const estimationData = getDataFromNodeErrorMessage(error.message)
|
||||
|
||||
if (!estimationData || sameString(estimationData, EMPTY_DATA)) {
|
||||
throw error
|
||||
}
|
||||
|
||||
return new BigNumber(estimationData.substring(138), 16).toNumber()
|
||||
}
|
||||
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<number> => {
|
||||
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(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<number> => {
|
||||
// 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)
|
||||
}
|
||||
|
||||
type SafeTxGasEstimationProps = {
|
||||
safeAddress: string
|
||||
|
@ -160,7 +14,6 @@ type SafeTxGasEstimationProps = {
|
|||
txRecipient: string
|
||||
txAmount: string
|
||||
operation: number
|
||||
safeTxGas?: number
|
||||
}
|
||||
|
||||
export const estimateSafeTxGas = async ({
|
||||
|
@ -169,7 +22,6 @@ export const estimateSafeTxGas = async ({
|
|||
txRecipient,
|
||||
txAmount,
|
||||
operation,
|
||||
safeTxGas,
|
||||
}: SafeTxGasEstimationProps): Promise<number> => {
|
||||
try {
|
||||
const safeTxGasEstimation = await fetchSafeTxGasEstimation({
|
||||
|
@ -180,15 +32,6 @@ export const estimateSafeTxGas = async ({
|
|||
operation,
|
||||
})
|
||||
|
||||
console.log('Backend gas estimation', safeTxGasEstimation)
|
||||
|
||||
if (safeTxGas) {
|
||||
// If safeTxGas was already defined we leave it but log our estimation for debug purposes
|
||||
console.info('This is the smart contract minimum expected safeTxGas', safeTxGasEstimation)
|
||||
// We return set safeTxGas
|
||||
return safeTxGas
|
||||
}
|
||||
|
||||
return parseInt(safeTxGasEstimation)
|
||||
} catch (error) {
|
||||
console.info('Error calculating tx gas estimation', error.message)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState, useRef, useCallback, useEffect } from 'react'
|
||||
import React, { ReactElement, useState, useRef, useCallback, useEffect } from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { FixedIcon, Loader, Title, Card } from '@gnosis.pm/safe-react-components'
|
||||
import { MethodToResponse, RPCPayload } from '@gnosis.pm/safe-apps-sdk'
|
||||
|
@ -82,7 +82,7 @@ const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = {
|
|||
params: undefined,
|
||||
}
|
||||
|
||||
const AppFrame = ({ appUrl }: Props): React.ReactElement => {
|
||||
const AppFrame = ({ appUrl }: Props): ReactElement => {
|
||||
const granted = useSelector(grantedSelector)
|
||||
const safeAddress = useSelector(safeParamAddressFromStateSelector)
|
||||
const ethBalance = useSelector(safeEthBalanceSelector)
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import React, { ReactElement, useEffect, useMemo, useState } from 'react'
|
||||
import { Text, EthHashInfo, ModalFooterConfirmation } from '@gnosis.pm/safe-react-components'
|
||||
import styled from 'styled-components'
|
||||
import { useDispatch } from 'react-redux'
|
||||
|
||||
import TextBox from 'src/components/TextBox'
|
||||
import ModalTitle from 'src/components/ModalTitle'
|
||||
import Heading from 'src/components/layout/Heading'
|
||||
import { createTransaction } from 'src/logic/safe/store/actions/createTransaction'
|
||||
import { MULTI_SEND_ADDRESS } from 'src/logic/contracts/safeContracts'
|
||||
import { DELEGATE_CALL, TX_NOTIFICATION_TYPES, CALL } from 'src/logic/safe/transactions'
|
||||
|
@ -26,16 +24,11 @@ import { getExplorerInfo } from 'src/config'
|
|||
import Block from 'src/components/layout/Block'
|
||||
import Divider from 'src/components/Divider'
|
||||
|
||||
import GasEstimationInfo from '../GasEstimationInfo'
|
||||
import { ConfirmTxModalProps, DecodedTxDetail } from '.'
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const StyledTextBox = styled(TextBox)`
|
||||
max-width: 444px;
|
||||
`
|
||||
|
||||
const Container = styled.div`
|
||||
max-width: 480px;
|
||||
padding: ${md} ${lg} 0;
|
||||
|
@ -90,8 +83,7 @@ export const ReviewConfirm = ({
|
|||
onTxReject,
|
||||
areTxsMalformed,
|
||||
showDecodedTxData,
|
||||
}: Props): React.ReactElement => {
|
||||
const [estimatedSafeTxGas, setEstimatedSafeTxGas] = useState(0)
|
||||
}: Props): ReactElement => {
|
||||
const isMultiSend = txs.length > 1
|
||||
const [decodedData, setDecodedData] = useState<DecodedData | null>(null)
|
||||
const dispatch = useDispatch()
|
||||
|
@ -133,12 +125,6 @@ export const ReviewConfirm = ({
|
|||
manualGasLimit,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (params?.safeTxGas) {
|
||||
setEstimatedSafeTxGas(gasEstimation)
|
||||
}
|
||||
}, [params, gasEstimation])
|
||||
|
||||
// Decode tx data.
|
||||
useEffect(() => {
|
||||
const decodeTxData = async () => {
|
||||
|
@ -171,9 +157,7 @@ export const ReviewConfirm = ({
|
|||
origin: app.id,
|
||||
navigateToTransactionsTab: false,
|
||||
txNonce: txParameters.safeNonce,
|
||||
safeTxGas: txParameters.safeTxGas
|
||||
? Number(txParameters.safeTxGas)
|
||||
: Math.max(params?.safeTxGas || 0, estimatedSafeTxGas),
|
||||
safeTxGas: txParameters.safeTxGas ? Number(txParameters.safeTxGas) : undefined,
|
||||
ethParameters: txParameters,
|
||||
notifiedTransaction: TX_NOTIFICATION_TYPES.STANDARD_TX,
|
||||
},
|
||||
|
@ -206,7 +190,7 @@ export const ReviewConfirm = ({
|
|||
<EditableTxParameters
|
||||
ethGasLimit={gasLimit}
|
||||
ethGasPrice={gasPriceFormatted}
|
||||
safeTxGas={gasEstimation.toString()}
|
||||
safeTxGas={Math.max(gasEstimation, params?.safeTxGas || 0).toString()}
|
||||
closeEditModalCallback={closeEditModalCallback}
|
||||
isOffChainSignature={isOffChainSignature}
|
||||
isExecution={isExecution}
|
||||
|
@ -238,18 +222,6 @@ export const ReviewConfirm = ({
|
|||
<DecodeTxs txs={txs} decodedData={decodedData} onTxItemClick={showDecodedTxData} />
|
||||
</DecodeTxsWrapper>
|
||||
{!isMultiSend && <Divider />}
|
||||
{/* Warning gas estimation */}
|
||||
{params?.safeTxGas && (
|
||||
<div className="section">
|
||||
<Heading tag="h3">SafeTxGas</Heading>
|
||||
<StyledTextBox>{params?.safeTxGas}</StyledTextBox>
|
||||
<GasEstimationInfo
|
||||
appEstimation={params.safeTxGas}
|
||||
internalEstimation={estimatedSafeTxGas}
|
||||
loading={txEstimationExecutionStatus === EstimationStatus.LOADING}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{/* Tx Parameters */}
|
||||
<TxParametersDetail
|
||||
txParameters={txParameters}
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import Img from 'src/components/layout/Img'
|
||||
import CheckIcon from 'src/assets/icons/check.svg'
|
||||
import AlertIcon from 'src/assets/icons/alert.svg'
|
||||
|
||||
type OwnProps = {
|
||||
appEstimation: number
|
||||
internalEstimation: number
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const imgStyles = {
|
||||
marginRight: '5px',
|
||||
}
|
||||
|
||||
const GasEstimationInfo = ({ appEstimation, internalEstimation, loading }: OwnProps): React.ReactElement => {
|
||||
if (loading) {
|
||||
return <p>Checking transaction parameters...</p>
|
||||
}
|
||||
|
||||
let content: React.ReactElement | null = null
|
||||
if (appEstimation >= internalEstimation) {
|
||||
content = (
|
||||
<>
|
||||
<Img alt="Success" src={CheckIcon} style={imgStyles} /> Gas estimation is OK
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
if (internalEstimation === 0) {
|
||||
content = (
|
||||
<>
|
||||
<Img alt="Warning" src={AlertIcon} style={imgStyles} /> Error while estimating gas. The transaction may fail.
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return <Container>{content}</Container>
|
||||
}
|
||||
|
||||
export default GasEstimationInfo
|
Loading…
Reference in New Issue