Skip to content

Commit

Permalink
⚠️ [ONC-19] Error on failed gas estimation (#7569)
Browse files Browse the repository at this point in the history
  • Loading branch information
raymondjacobson authored Feb 15, 2024
1 parent 58fd090 commit 23edd81
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 28 deletions.
8 changes: 6 additions & 2 deletions packages/discovery-provider/src/utils/multi_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ def __init__(self, providers):
self.providers = [HTTPProvider(provider) for provider in providers.split(",")]

def make_request(self, method, params):
last_exception = None
for provider in random.sample(self.providers, k=len(self.providers)):
try:
return provider.make_request(method, params)
except Exception:
except Exception as e:
last_exception = e
continue
raise Exception("All requests failed")
raise (
last_exception if last_exception else Exception("No RPC providers found")
)

def isConnected(self):
return any(provider.isConnected() for provider in self.providers)
Expand Down
7 changes: 4 additions & 3 deletions packages/libs/src/services/ethWeb3Manager/EthWeb3Manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,16 @@ export class EthWeb3Manager {
txRetries = 5,
txGasLimit: number | null = null
): Promise<TransactionReceipt> {
const internalWallet = contractAddress && privateKey
const gasLimit =
txGasLimit ??
(await estimateGas({
method: contractMethod,
from: this.ownerWallet,
gasLimitMaximum: MAX_GAS_LIMIT
gasLimitMaximum: MAX_GAS_LIMIT,
shouldThrowIfGasEstimationFails: !internalWallet
}))
if (contractAddress && privateKey) {
if (internalWallet) {
let gasPrice = parseInt(await this.web3.eth.getGasPrice())
if (isNaN(gasPrice) || gasPrice > HIGH_GAS_PRICE) {
gasPrice = DEFAULT_GAS_PRICE
Expand Down Expand Up @@ -160,7 +162,6 @@ export class EthWeb3Manager {
const gasPrice = parseInt(await this.web3.eth.getGasPrice())
return await contractMethod.send({
from: this.ownerWallet,
gas: gasLimit,
gasPrice
})
}
Expand Down
24 changes: 15 additions & 9 deletions packages/libs/src/utils/estimateGas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,34 @@ export interface ContractMethod {
encodeABI: () => string
send: <Tx>(config: {
from: Wallet | string | undefined
gas: number
gas?: number
gasPrice?: number
}) => Tx
}

interface EstimateGasConfig {
// The contract method
method: ContractMethod
// Address the method will be sent from (required if the contract requires a certain sender, e.g. guardian)
from?: Wallet | string
// The maximum amount of gas we will allow (likely will return a number much smaller than this)
gasLimitMaximum: number
// The multiplier to safe-guard against estimates that are too low
multiplier?: number
// Whether or not to throw if gas estimation fails. Usually this signals that a
// contract call's simulation failed and likely the actual contract call will fail.
shouldThrowIfGasEstimationFails?: boolean
}

/**
* Returns estimated gas use for a txn for a contract method
* @param options
* @param options.method the contract method
* @param options.from address the method will be sent from (required if the contract requires a certain sender, e.g. guardian)
* @param options.gasLimitMaximum the maximum amount of gas we will allow
* (likely will return a number much smaller than this)
* @param options.multipler the multiplier to safe-guard against estimates that are too low
*/
export const estimateGas = async ({
method,
from,
gasLimitMaximum,
multiplier = GAS_LIMIT_MULTIPLIER
multiplier = GAS_LIMIT_MULTIPLIER,
shouldThrowIfGasEstimationFails = false
}: EstimateGasConfig) => {
try {
const estimatedGas = await method.estimateGas({
Expand All @@ -57,8 +59,12 @@ export const estimateGas = async ({
return safeEstimatedGas
} catch (e) {
console.error(
`Unable to estimate gas for transaction ${method._method.name}, using ${gasLimitMaximum}`
`Unable to estimate gas for transaction ${method._method.name}`,
e
)
if (shouldThrowIfGasEstimationFails) {
throw e
}
return gasLimitMaximum
}
}
6 changes: 4 additions & 2 deletions packages/libs/src/utils/multiProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,18 @@ export class MultiProvider extends Web3.providers.HttpProvider {
* @param {Object} payload
*/
async _send(payload: JsonRpcPayload) {
let lastError: Error | null = null

for (const provider of shuffle(this.providers)) {
try {
const send = promisify(getSendMethod(provider).bind(provider))
const result = await send(payload)
return result
} catch (e) {
console.info(e)
lastError = e as Error
}
}

throw new Error('All requests failed')
throw lastError || new Error('No RPC providers found')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ const ConfirmTransactionModal: React.FC<ConfirmTransactionModalProps> = ({
status,
error
}: ConfirmTransactionModalProps) => {
const formattedError = error.includes('\n') ? error.split('\n')[0] : error
return (
<Modal
title={messages.title}
Expand Down Expand Up @@ -260,7 +261,9 @@ const ConfirmTransactionModal: React.FC<ConfirmTransactionModalProps> = ({
) : (
<>
<div className={styles.errorHeader}>{messages.errorHeader}</div>
<SimpleBar className={styles.scrollableMessage}>{error}</SimpleBar>
<SimpleBar className={styles.scrollableMessage}>
{formattedError}
</SimpleBar>
<Button
text={messages.okay}
type={ButtonType.PRIMARY}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const WaitingTransaction: React.FC<WaitingTransactionProps> = props => {
setError,
setStatus,
cancelTransaction
} = useCancelTransaction(props.name)
} = useCancelTransaction(props.name, !isOpen)

const onCloseModal = useCallback(() => {
setStatus(undefined)
Expand Down Expand Up @@ -170,12 +170,12 @@ const ReadyTransaction: React.FC<ReadyTransactionProps> = props => {
status: cancelStatus,
error: cancelError,
cancelTransaction
} = useCancelTransaction(props.name)
} = useCancelTransaction(props.name, !isCancelOpen)
const {
status: submitStatus,
error: submitError,
submitTransaction
} = useSubmitTransaction(props.name)
} = useSubmitTransaction(props.name, !isSubmitOpen)

useEffect(() => {
if (cancelStatus === Status.Success || submitStatus === Status.Success) {
Expand Down
21 changes: 15 additions & 6 deletions protocol-dashboard/src/store/actions/cancelTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback } from 'react'
import { useState, useCallback, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { Action } from 'redux'
Expand All @@ -25,7 +25,7 @@ function cancelDecreaseStake(

setStatus(Status.Success)
} catch (error) {
console.log(error)
setError(error.message)
setStatus(Status.Failure)
}
}
Expand All @@ -47,7 +47,7 @@ function cancelUpdateOperatorCut(

setStatus(Status.Success)
} catch (error) {
console.log(error)
setError(error.message)
setStatus(Status.Failure)
}
}
Expand All @@ -68,7 +68,7 @@ function cancelUndelegate(

setStatus(Status.Success)
} catch (error) {
console.log(error)
setError(error.message)
setStatus(Status.Failure)
}
}
Expand All @@ -92,16 +92,25 @@ function cancelRemoveDelegator(

setStatus(Status.Success)
} catch (error) {
console.log(error)
setError(error.message)
setStatus(Status.Failure)
}
}
}

export const useCancelTransaction = (name: PendingTransactionName) => {
export const useCancelTransaction = (
name: PendingTransactionName,
shouldReset: boolean
) => {
const [status, setStatus] = useState<undefined | Status>()
const [error, setError] = useState<string>('')
const dispatch: ThunkDispatch<AppState, Audius, AnyAction> = useDispatch()
useEffect(() => {
if (shouldReset) {
setStatus(undefined)
setError('')
}
}, [shouldReset, setStatus, setError])

const cancelTransaction = useCallback(
(wallet?: Address) => {
Expand Down
13 changes: 11 additions & 2 deletions protocol-dashboard/src/store/actions/submitTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useCallback } from 'react'
import { useState, useCallback, useEffect } from 'react'
import { useDispatch } from 'react-redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { Action } from 'redux'
Expand Down Expand Up @@ -109,10 +109,19 @@ function removeDelegator(
}
}

export const useSubmitTransaction = (name: PendingTransactionName) => {
export const useSubmitTransaction = (
name: PendingTransactionName,
shouldReset: boolean
) => {
const [status, setStatus] = useState<undefined | Status>()
const [error, setError] = useState<string>('')
const dispatch: ThunkDispatch<AppState, Audius, AnyAction> = useDispatch()
useEffect(() => {
if (shouldReset) {
setStatus(undefined)
setError('')
}
}, [shouldReset, setStatus, setError])

const submitTransaction = useCallback(
(wallet?: Address) => {
Expand Down

0 comments on commit 23edd81

Please sign in to comment.