This repository has been archived by the owner on Nov 10, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 362
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix: safeTxGasEstimation race condition (#3683)
* Fix: safeTxGasEstimation race condition * Fix tests * Add useAsync tests * Rm redundant check * Don't estimate safeTxGas if no txData * Set baseGas and gasPrice in createTransaction * Pass safeTxGas to gas limit estimation * Remove pedning if receipt status false * Restore getRecommendedNonce * Fix tests * Revert pending watcher changes * Add a warning on tx fail
- Loading branch information
Showing
8 changed files
with
222 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
import { renderHook } from '@testing-library/react-hooks' | ||
import useAsync from '../useAsync' | ||
|
||
describe('useAsync tests', () => { | ||
it('returns a successful result', async () => { | ||
const fakeCallback = jest.fn(() => Promise.resolve('success')) | ||
|
||
const { result, waitForNextUpdate } = renderHook(() => useAsync(fakeCallback)) | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(result.current).toEqual({ result: 'success', error: undefined }) | ||
|
||
expect(fakeCallback).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
it('returns an error', async () => { | ||
const fakeCallback = jest.fn(() => Promise.reject(new Error('failure'))) | ||
|
||
const { result, waitForNextUpdate } = renderHook(() => useAsync(fakeCallback)) | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(result.current.result).toBe(undefined) | ||
expect(result.current.error).toBeDefined() | ||
expect(result.current.error!.message).toBe('failure') | ||
|
||
expect(fakeCallback).toHaveBeenCalledTimes(1) | ||
}) | ||
|
||
it('ignores a stale result if a new request has been launched already', async () => { | ||
// This will resolve AFTER the second callback but the result should be ignored | ||
const fakeCallback1 = jest.fn(() => { | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
resolve('success 1') | ||
}, 10) | ||
}) | ||
}) | ||
|
||
const fakeCallback2 = jest.fn(() => Promise.resolve('success 2')) | ||
|
||
let counter = 0 | ||
const { result, waitForNextUpdate, rerender } = renderHook(() => { | ||
const callback = counter > 0 ? fakeCallback2 : fakeCallback1 | ||
counter += 1 | ||
return useAsync(callback) | ||
}) | ||
|
||
expect(result.current.result).toBe(undefined) | ||
expect(result.current.error).toBe(undefined) | ||
|
||
rerender() // re-render immediately | ||
|
||
await waitForNextUpdate() | ||
|
||
expect(result.current.result).toBe('success 2') | ||
expect(result.current.error).toBe(undefined) | ||
|
||
expect(fakeCallback1).toHaveBeenCalledTimes(1) | ||
expect(fakeCallback2).toHaveBeenCalledTimes(1) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import { useEffect, useState } from 'react' | ||
|
||
type AsyncResult<T> = { | ||
error: Error | undefined | ||
result: T | undefined | ||
} | ||
|
||
const useAsync = <T>(asyncCall: () => Promise<T>): AsyncResult<T> => { | ||
const [asyncVal, setAsyncVal] = useState<T>() | ||
const [err, setErr] = useState<Error>() | ||
|
||
useEffect(() => { | ||
let isCurrent = true | ||
|
||
setAsyncVal(undefined) | ||
setErr(undefined) | ||
|
||
asyncCall() | ||
.then((val: T) => { | ||
if (isCurrent) setAsyncVal(val) | ||
}) | ||
.catch((error) => { | ||
if (isCurrent) setErr(error) | ||
}) | ||
|
||
return () => { | ||
isCurrent = false | ||
} | ||
}, [asyncCall]) | ||
|
||
return { error: err, result: asyncVal } | ||
} | ||
|
||
export default useAsync |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,19 @@ | ||
import { SafeTransactionEstimation } from '@gnosis.pm/safe-react-gateway-sdk' | ||
import { useEffect, useState } from 'react' | ||
import { useCallback } from 'react' | ||
import { useSelector } from 'react-redux' | ||
import { getRecommendedNonce } from 'src/logic/safe/api/fetchSafeTxGasEstimation' | ||
import { getLastTxNonce } from 'src/logic/safe/store/selectors/gatewayTransactions' | ||
import useAsync from 'src/logic/hooks/useAsync' | ||
|
||
const useRecommendedNonce = (safeAddress: string): number => { | ||
const lastTxNonce = useSelector(getLastTxNonce) | ||
const [recommendedNonce, setRecommendedNonce] = useState<number>(lastTxNonce ? lastTxNonce + 1 : 0) | ||
const lastTxNonce = useSelector(getLastTxNonce) || -1 | ||
|
||
useEffect(() => { | ||
let isCurrent = true | ||
const getNonce = useCallback(() => { | ||
return getRecommendedNonce(safeAddress) | ||
}, [safeAddress]) | ||
|
||
const fetchRecommendedNonce = async () => { | ||
let recommendedNonce: SafeTransactionEstimation['recommendedNonce'] | ||
try { | ||
recommendedNonce = await getRecommendedNonce(safeAddress) | ||
} catch (e) { | ||
return | ||
} | ||
const { result } = useAsync<number>(getNonce) | ||
|
||
if (isCurrent) { | ||
setRecommendedNonce(recommendedNonce) | ||
} | ||
} | ||
fetchRecommendedNonce() | ||
|
||
return () => { | ||
isCurrent = false | ||
} | ||
}, [lastTxNonce, safeAddress]) | ||
|
||
return recommendedNonce | ||
return result == null ? lastTxNonce + 1 : result | ||
} | ||
|
||
export default useRecommendedNonce |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.