Fix gas estimation (#1959)

* Update gas estimation to handle extra data for execution

* Tweak safe gas calculation

* Optimize send ETH transaction estimation
This commit is contained in:
Daniel Sanchez 2021-03-01 16:10:12 +01:00 committed by GitHub
parent ae8175aae2
commit 5020c0daa3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 47 additions and 27 deletions

View File

@ -4,8 +4,8 @@ import {
estimateGasForTransactionApproval, estimateGasForTransactionApproval,
estimateGasForTransactionCreation, estimateGasForTransactionCreation,
estimateGasForTransactionExecution, estimateGasForTransactionExecution,
MINIMUM_TRANSACTION_GAS, getFixedGasCosts,
GAS_REQUIRED_PER_SIGNATURE, SAFE_TX_GAS_DATA_COST,
} from 'src/logic/safe/transactions/gas' } from 'src/logic/safe/transactions/gas'
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue' import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
import { formatAmount } from 'src/logic/tokens/utils/formatAmount' import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
@ -213,6 +213,8 @@ export const useEstimateTransactionGas = ({
preApprovingOwner, preApprovingOwner,
) )
const fixedGasCosts = getFixedGasCosts(Number(threshold))
try { try {
const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion) const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)
@ -233,10 +235,10 @@ export const useEstimateTransactionGas = ({
const gasPrice = manualGasPrice ? web3.utils.toWei(manualGasPrice, 'gwei') : await calculateGasPrice() const gasPrice = manualGasPrice ? web3.utils.toWei(manualGasPrice, 'gwei') : await calculateGasPrice()
const gasPriceFormatted = web3.utils.fromWei(gasPrice, 'gwei') const gasPriceFormatted = web3.utils.fromWei(gasPrice, 'gwei')
const estimatedGasCosts = gasEstimation * parseInt(gasPrice, 10) const estimatedGasCosts = (gasEstimation + fixedGasCosts) * parseInt(gasPrice, 10)
const gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals) const gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
const gasCostFormatted = formatAmount(gasCost) const gasCostFormatted = formatAmount(gasCost)
const gasLimit = (gasEstimation * 2).toString() const gasLimit = ((gasEstimation + fixedGasCosts) * 2).toString()
let txEstimationExecutionStatus = EstimationStatus.SUCCESS let txEstimationExecutionStatus = EstimationStatus.SUCCESS
@ -259,7 +261,7 @@ export const useEstimateTransactionGas = ({
} catch (error) { } catch (error) {
console.warn(error.message) console.warn(error.message)
// We put a fixed the amount of gas to let the user try to execute the tx, but it's not accurate so it will probably fail // We put a fixed the amount of gas to let the user try to execute the tx, but it's not accurate so it will probably fail
const gasEstimation = MINIMUM_TRANSACTION_GAS + (threshold || 1) * GAS_REQUIRED_PER_SIGNATURE const gasEstimation = fixedGasCosts + SAFE_TX_GAS_DATA_COST
const gasCost = fromTokenUnit(gasEstimation, nativeCoin.decimals) const gasCost = fromTokenUnit(gasEstimation, nativeCoin.decimals)
const gasCostFormatted = formatAmount(gasCost) const gasCostFormatted = formatAmount(gasCost)
setGasEstimation({ setGasEstimation({

View File

@ -84,10 +84,11 @@ export const createTransaction = (
const isExecution = await shouldExecuteTransaction(safeInstance, nonce, lastTx) const isExecution = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
const safeVersion = await getCurrentSafeVersion(safeInstance) const safeVersion = await getCurrentSafeVersion(safeInstance)
let safeTxGas let safeTxGas = safeTxGasArg || 0
try { try {
safeTxGas = if (safeTxGasArg === undefined) {
safeTxGasArg || (await estimateGasForTransactionCreation(safeAddress, txData, to, valueInWei, operation)) safeTxGas = await estimateGasForTransactionCreation(safeAddress, txData, to, valueInWei, operation)
}
} catch (error) { } catch (error) {
safeTxGas = safeTxGasArg || 0 safeTxGas = safeTxGasArg || 0
} }

View File

@ -15,6 +15,10 @@ import { sameString } from 'src/utils/strings'
export const MINIMUM_TRANSACTION_GAS = 21000 export const MINIMUM_TRANSACTION_GAS = 21000
// Estimation of gas required for each signature (aproximately 7800, roundup to 8000) // Estimation of gas required for each signature (aproximately 7800, roundup to 8000)
export const GAS_REQUIRED_PER_SIGNATURE = 8000 export const GAS_REQUIRED_PER_SIGNATURE = 8000
// We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
// We also add 3k pay when processing safeTxGas value. We don't know this value when creating the transaction
// Hex values different than 0 has some gas cost
export const SAFE_TX_GAS_DATA_COST = 6000
// Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount // Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount
const parseRequiredTxGasResponse = (data: string): number => { const parseRequiredTxGasResponse = (data: string): number => {
@ -151,7 +155,7 @@ const estimateGasWithRPCCall = async (txConfig: {
const { error } = data const { error } = data
if (error?.data) { if (error?.data) {
return new BigNumber(data.error.data.substring(138), 16).toNumber() return new BigNumber(error.data.substring(138), 16).toNumber()
} }
} catch (error) { } catch (error) {
console.log('Gas estimation endpoint errored: ', error.message) console.log('Gas estimation endpoint errored: ', error.message)
@ -178,33 +182,41 @@ const calculateMinimumGasForTransaction = async (
additionalGasBatches: number[], additionalGasBatches: number[],
safeAddress: string, safeAddress: string,
estimateData: string, estimateData: string,
txGasEstimation: number, safeTxGasEstimation: number,
dataGasEstimation: number,
fixedGasCosts: number, fixedGasCosts: number,
): Promise<number> => { ): Promise<number> => {
for (const additionalGas of additionalGasBatches) { for (const additionalGas of additionalGasBatches) {
const amountOfGasToTryTx = txGasEstimation + dataGasEstimation + fixedGasCosts + additionalGas const batchedSafeTxGas = safeTxGasEstimation + additionalGas
console.info(`Estimating transaction creation with gas amount: ${amountOfGasToTryTx}`) // To simulate if safeTxGas is enough we need to send an estimated gasLimit that will be the sum
// of the safeTxGasEstimation and fixedGas costs for ethereum transaction
const gasLimit = batchedSafeTxGas + fixedGasCosts
console.info(`Estimating safeTxGas with gas amount: ${batchedSafeTxGas}`)
try { try {
const estimation = await getGasEstimationTxResponse({ const estimation = await getGasEstimationTxResponse({
to: safeAddress, to: safeAddress,
from: safeAddress, from: safeAddress,
data: estimateData, data: estimateData,
gasPrice: 0, gasPrice: 0,
gas: amountOfGasToTryTx, gas: gasLimit,
}) })
if (estimation > 0) { if (estimation > 0) {
console.info(`Gas estimation successfully finished with gas amount: ${amountOfGasToTryTx}`) console.info(`Gas estimation successfully finished with gas amount: ${batchedSafeTxGas}`)
return amountOfGasToTryTx return batchedSafeTxGas
} }
} catch (error) { } catch (error) {
console.log(`Error trying to estimate gas with amount: ${amountOfGasToTryTx}`) console.log(`Error trying to estimate gas with amount: ${batchedSafeTxGas}`)
} }
} }
return 0 return 0
} }
export const getFixedGasCosts = (threshold: number): number => {
// There are some minimum gas costs to execute an Ethereum transaction
// We add this fixed network minimum gas, the gas required to check each signature
return MINIMUM_TRANSACTION_GAS + (threshold || 1) * GAS_REQUIRED_PER_SIGNATURE
}
export const estimateGasForTransactionCreation = async ( export const estimateGasForTransactionCreation = async (
safeAddress: string, safeAddress: string,
data: string, data: string,
@ -217,32 +229,35 @@ export const estimateGasForTransactionCreation = async (
const safeInstance = await getGnosisSafeInstanceAt(safeAddress) const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const estimateData = safeInstance.methods.requiredTxGas(to, valueInWei, data, operation).encodeABI() const estimateData = safeInstance.methods.requiredTxGas(to, valueInWei, data, operation).encodeABI()
const threshold = await safeInstance.methods.getThreshold().call()
const fixedGasCosts = getFixedGasCosts(Number(threshold))
const gasEstimationResponse = await getGasEstimationTxResponse({ const gasEstimationResponse = await getGasEstimationTxResponse({
to: safeAddress, to: safeAddress,
from: safeAddress, from: safeAddress,
data: estimateData, data: estimateData,
gas: safeTxGas ? safeTxGas : undefined, gas: safeTxGas ? safeTxGas + fixedGasCosts : undefined,
}) })
if (safeTxGas) { if (safeTxGas) {
return gasEstimationResponse // When we execute we get a more precise estimate value, we log for debug purposes
console.info('This is the smart contract minimum expected safeTxGas', gasEstimationResponse)
// We return set safeTxGas
return safeTxGas
} }
const threshold = await safeInstance.methods.getThreshold().call()
const dataGasEstimation = parseRequiredTxGasResponse(estimateData) const dataGasEstimation = parseRequiredTxGasResponse(estimateData)
// We add the minimum required gas for a transaction // Adding this values we should get the full safeTxGas value
// TODO: This fix will be more accurate when we have a service for estimation. const safeTxGasEstimation = gasEstimationResponse + dataGasEstimation + SAFE_TX_GAS_DATA_COST
// This fix takes the safe threshold and multiplies it by GAS_REQUIRED_PER_SIGNATURE. // We will add gas batches in case is not enough
const fixedGasCosts = MINIMUM_TRANSACTION_GAS + (Number(threshold) || 1) * GAS_REQUIRED_PER_SIGNATURE
const additionalGasBatches = [0, 10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000, 5120000] const additionalGasBatches = [0, 10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000, 5120000]
return await calculateMinimumGasForTransaction( return await calculateMinimumGasForTransaction(
additionalGasBatches, additionalGasBatches,
safeAddress, safeAddress,
estimateData, estimateData,
gasEstimationResponse, safeTxGasEstimation,
dataGasEstimation,
fixedGasCosts, fixedGasCosts,
) )
} catch (error) { } catch (error) {
@ -290,6 +305,7 @@ export const estimateGasForTransactionExecution = async ({
txRecipient, txRecipient,
txAmount, txAmount,
operation, operation,
safeTxGas,
) )
console.info(`Gas estimation successfully finished with gas amount: ${gasEstimation}`) console.info(`Gas estimation successfully finished with gas amount: ${gasEstimation}`)
return gasEstimation return gasEstimation

View File

@ -114,6 +114,7 @@ const ReviewSendFundsTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactE
txData: data, txData: data,
txRecipient, txRecipient,
txType: tx.txType, txType: tx.txType,
txAmount: txValue,
safeTxGas: manualSafeTxGas, safeTxGas: manualSafeTxGas,
manualGasPrice, manualGasPrice,
}) })