Skip to content

Commit cf58f89

Browse files
authored
fix: resolves #3513 (#3530)
1 parent 77a0938 commit cf58f89

8 files changed

+77
-110
lines changed

.changeset/witty-wombats-taste.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"viem": patch
3+
---
4+
5+
Optimized OP Stack gas & fee estimation, and resolved #3513.

src/op-stack/actions/estimateContractL1Fee.test.ts

-24
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,3 @@ test('default', async () => {
1717
}),
1818
).toBeDefined()
1919
})
20-
21-
test('revert', async () => {
22-
await expect(() =>
23-
estimateContractL1Fee(optimismClient, {
24-
...usdcContractConfig,
25-
address: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
26-
account: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
27-
functionName: 'transfer',
28-
args: ['0xc8373edfad6d5c5f600b6b2507f78431c5271ff5', 1n],
29-
}),
30-
).rejects.toMatchInlineSnapshot(`
31-
[ContractFunctionExecutionError: The contract function "transfer" reverted with the following reason:
32-
ERC20: transfer amount exceeds balance
33-
34-
Contract Call:
35-
address: 0x0000000000000000000000000000000000000000
36-
function: transfer(address recipient, uint256 amount)
37-
args: (0xc8373edfad6d5c5f600b6b2507f78431c5271ff5, 1)
38-
sender: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC
39-
40-
Docs: https://viem.sh/docs/chains/op-stack/estimateContractL1Fee
41-
42-
`)
43-
})

src/op-stack/actions/estimateContractL1Gas.test.ts

+1-25
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,5 @@ test('default', async () => {
1515
functionName: 'transfer',
1616
args: ['0xc8373edfad6d5c5f600b6b2507f78431c5271ff5', 1n],
1717
}),
18-
).toBe(2572n)
19-
})
20-
21-
test('revert', async () => {
22-
await expect(() =>
23-
estimateContractL1Gas(optimismClient, {
24-
...usdcContractConfig,
25-
address: '0x0b2c639c533813f4aa9d7837caf62653d097ff85',
26-
account: '0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
27-
functionName: 'transfer',
28-
args: ['0xc8373edfad6d5c5f600b6b2507f78431c5271ff5', 1n],
29-
}),
30-
).rejects.toMatchInlineSnapshot(`
31-
[ContractFunctionExecutionError: The contract function "transfer" reverted with the following reason:
32-
ERC20: transfer amount exceeds balance
33-
34-
Contract Call:
35-
address: 0x0000000000000000000000000000000000000000
36-
function: transfer(address recipient, uint256 amount)
37-
args: (0xc8373edfad6d5c5f600b6b2507f78431c5271ff5, 1)
38-
sender: 0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC
39-
40-
Docs: https://viem.sh/docs/chains/op-stack/estimateContractL1Gas
41-
42-
`)
18+
).toBe(2544n)
4319
})

src/op-stack/actions/estimateL1Fee.test.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
1+
import { Hex } from 'ox'
12
import { expect, test } from 'vitest'
23

34
import { accounts } from '~test/src/constants.js'
45

56
import { anvilOptimism } from '../../../test/src/anvil.js'
6-
import { type TransactionRequestEIP1559, parseGwei } from '../../index.js'
7-
import { parseEther } from '../../utils/unit/parseEther.js'
7+
import { optimism } from '../../chains/index.js'
8+
import {
9+
http,
10+
type TransactionRequestEIP1559,
11+
createClient,
12+
} from '../../index.js'
813
import { estimateL1Fee } from './estimateL1Fee.js'
914

1015
const optimismClient = anvilOptimism.getClient()
1116
const optimismClientWithAccount = anvilOptimism.getClient({ account: true })
1217
const optimismClientWithoutChain = anvilOptimism.getClient({ chain: false })
1318

1419
const baseTransaction = {
15-
maxFeePerGas: parseGwei('100'),
16-
maxPriorityFeePerGas: parseGwei('1'),
20+
data: '0xdeadbeef',
1721
to: accounts[1].address,
18-
value: parseEther('0.1'),
1922
} as const satisfies Omit<TransactionRequestEIP1559, 'from'>
2023

2124
test('default', async () => {
@@ -68,3 +71,15 @@ test('args: nullish chain', async () => {
6871
})
6972
expect(fee).toBeDefined()
7073
})
74+
75+
test('behavior: account with no funds', async () => {
76+
const optimismClient = createClient({
77+
chain: optimism,
78+
transport: http(),
79+
})
80+
const gas = await estimateL1Fee(optimismClient, {
81+
...baseTransaction,
82+
account: Hex.random(20),
83+
})
84+
expect(gas).toBeDefined()
85+
})

src/op-stack/actions/estimateL1Fee.ts

+11-21
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import {
44
type ReadContractErrorType,
55
readContract,
66
} from '../../actions/public/readContract.js'
7-
import {
8-
type PrepareTransactionRequestErrorType,
9-
type PrepareTransactionRequestParameters,
10-
prepareTransactionRequest,
11-
} from '../../actions/wallet/prepareTransactionRequest.js'
127
import type { Client } from '../../clients/createClient.js'
138
import type { Transport } from '../../clients/transports/createTransport.js'
149
import type { ErrorType } from '../../errors/utils.js'
@@ -21,15 +16,11 @@ import type {
2116
import type { RequestErrorType } from '../../utils/buildRequest.js'
2217
import { getChainContractAddress } from '../../utils/chain/getChainContractAddress.js'
2318
import type { HexToNumberErrorType } from '../../utils/encoding/fromHex.js'
24-
import {
25-
type AssertRequestErrorType,
26-
type AssertRequestParameters,
27-
assertRequest,
28-
} from '../../utils/transaction/assertRequest.js'
2919
import {
3020
type SerializeTransactionErrorType,
3121
serializeTransaction,
3222
} from '../../utils/transaction/serializeTransaction.js'
23+
import { parseGwei } from '../../utils/unit/parseGwei.js'
3324
import { gasPriceOracleAbi } from '../abis.js'
3425
import { contracts } from '../contracts.js'
3526

@@ -48,8 +39,6 @@ export type EstimateL1FeeReturnType = bigint
4839

4940
export type EstimateL1FeeErrorType =
5041
| RequestErrorType
51-
| PrepareTransactionRequestErrorType
52-
| AssertRequestErrorType
5342
| SerializeTransactionErrorType
5443
| HexToNumberErrorType
5544
| ReadContractErrorType
@@ -100,17 +89,18 @@ export async function estimateL1Fee<
10089
return contracts.gasPriceOracle.address
10190
})()
10291

103-
// Populate transaction with required fields to accurately estimate gas.
104-
const request = await prepareTransactionRequest(
105-
client,
106-
args as PrepareTransactionRequestParameters,
107-
)
108-
109-
assertRequest(request as AssertRequestParameters)
110-
11192
const transaction = serializeTransaction({
112-
...request,
93+
...args,
94+
chainId: chain?.id ?? 1,
11395
type: 'eip1559',
96+
97+
// Set upper-limit-ish stub values. Shouldn't affect the estimate too much as we are
98+
// tweaking dust bytes here (as opposed to long `data` bytes).
99+
// See: https://github.com/ethereum-optimism/optimism/blob/54d02df55523c9e1b4b38ed082c12a42087323a0/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L242-L248.
100+
gas: args.data ? 300_000n : 21_000n,
101+
maxFeePerGas: parseGwei('5'),
102+
maxPriorityFeePerGas: parseGwei('1'),
103+
nonce: 1,
114104
} as TransactionSerializable)
115105

116106
return readContract(client, {
+27-12
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,65 @@
1+
import { Hex } from 'ox'
12
import { expect, test } from 'vitest'
23

34
import { accounts } from '~test/src/constants.js'
45
import { anvilOptimism } from '../../../test/src/anvil.js'
5-
import { type TransactionRequestEIP1559, parseGwei } from '../../index.js'
6-
import { parseEther } from '../../utils/unit/parseEther.js'
6+
import { optimism } from '../../chains/index.js'
7+
import {
8+
http,
9+
type TransactionRequestEIP1559,
10+
createClient,
11+
} from '../../index.js'
712
import { estimateL1Gas } from './estimateL1Gas.js'
813

914
const optimismClient = anvilOptimism.getClient()
1015
const optimismClientWithAccount = anvilOptimism.getClient({ account: true })
1116
const optimismClientWithoutChain = anvilOptimism.getClient({ chain: false })
1217

1318
const baseTransaction = {
14-
maxFeePerGas: parseGwei('100'),
15-
maxPriorityFeePerGas: parseGwei('1'),
19+
data: '0xdeadbeef',
1620
to: accounts[1].address,
17-
value: parseEther('0.1'),
1821
} as const satisfies Omit<TransactionRequestEIP1559, 'from'>
1922

2023
test('default', async () => {
2124
const gas = await estimateL1Gas(optimismClientWithAccount, baseTransaction)
22-
expect(gas).toBe(2028n)
25+
expect(gas).toBe(2004n)
2326
})
2427

2528
test('minimal', async () => {
2629
const gas = await estimateL1Gas(optimismClientWithAccount, {})
27-
expect(gas).toBe(1600n)
30+
expect(gas).toBe(1604n)
2831
})
2932

3033
test('args: account', async () => {
3134
const gas = await estimateL1Gas(optimismClient, {
3235
...baseTransaction,
3336
account: accounts[0].address,
3437
})
35-
expect(gas).toBe(2028n)
38+
expect(gas).toBe(2004n)
3639
})
3740

3841
test('args: data', async () => {
3942
const gas = await estimateL1Gas(optimismClientWithAccount, {
4043
...baseTransaction,
4144
data: '0x00000000000000000000000000000000000000000000000004fefa17b7240000',
4245
})
43-
expect(gas).toBe(2244n)
46+
expect(gas).toBe(2156n)
4447
})
4548

4649
test('args: gasPriceOracleAddress', async () => {
4750
const gas = await estimateL1Gas(optimismClientWithAccount, {
4851
...baseTransaction,
4952
gasPriceOracleAddress: '0x420000000000000000000000000000000000000F',
5053
})
51-
expect(gas).toBe(2028n)
54+
expect(gas).toBe(2004n)
5255
})
5356

5457
test('args: nonce', async () => {
5558
const gas = await estimateL1Gas(optimismClientWithAccount, {
5659
...baseTransaction,
5760
nonce: 69,
5861
})
59-
expect(gas).toBe(2028n)
62+
expect(gas).toBe(2004n)
6063
})
6164

6265
test('args: nullish chain', async () => {
@@ -65,5 +68,17 @@ test('args: nullish chain', async () => {
6568
account: accounts[0].address,
6669
chain: null,
6770
})
68-
expect(gas).toBe(2028n)
71+
expect(gas).toBe(2004n)
72+
})
73+
74+
test('behavior: account with no funds', async () => {
75+
const optimismClient = createClient({
76+
chain: optimism,
77+
transport: http(),
78+
})
79+
const gas = await estimateL1Gas(optimismClient, {
80+
...baseTransaction,
81+
account: Hex.random(20),
82+
})
83+
expect(gas).toBeDefined()
6984
})

src/op-stack/actions/estimateL1Gas.ts

+11-21
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,6 @@ import {
44
type ReadContractErrorType,
55
readContract,
66
} from '../../actions/public/readContract.js'
7-
import {
8-
type PrepareTransactionRequestErrorType,
9-
type PrepareTransactionRequestParameters,
10-
prepareTransactionRequest,
11-
} from '../../actions/wallet/prepareTransactionRequest.js'
127
import type { Client } from '../../clients/createClient.js'
138
import type { Transport } from '../../clients/transports/createTransport.js'
149
import type { ErrorType } from '../../errors/utils.js'
@@ -21,15 +16,11 @@ import type {
2116
import type { RequestErrorType } from '../../utils/buildRequest.js'
2217
import { getChainContractAddress } from '../../utils/chain/getChainContractAddress.js'
2318
import type { HexToNumberErrorType } from '../../utils/encoding/fromHex.js'
24-
import {
25-
type AssertRequestErrorType,
26-
type AssertRequestParameters,
27-
assertRequest,
28-
} from '../../utils/transaction/assertRequest.js'
2919
import {
3020
type SerializeTransactionErrorType,
3121
serializeTransaction,
3222
} from '../../utils/transaction/serializeTransaction.js'
23+
import { parseGwei } from '../../utils/unit/parseGwei.js'
3324
import { gasPriceOracleAbi } from '../abis.js'
3425
import { contracts } from '../contracts.js'
3526

@@ -48,8 +39,6 @@ export type EstimateL1GasReturnType = bigint
4839

4940
export type EstimateL1GasErrorType =
5041
| RequestErrorType
51-
| PrepareTransactionRequestErrorType
52-
| AssertRequestErrorType
5342
| SerializeTransactionErrorType
5443
| HexToNumberErrorType
5544
| ReadContractErrorType
@@ -100,17 +89,18 @@ export async function estimateL1Gas<
10089
return contracts.gasPriceOracle.address
10190
})()
10291

103-
// Populate transaction with required fields to accurately estimate gas.
104-
const request = await prepareTransactionRequest(
105-
client,
106-
args as PrepareTransactionRequestParameters,
107-
)
108-
109-
assertRequest(request as AssertRequestParameters)
110-
11192
const transaction = serializeTransaction({
112-
...request,
93+
...args,
94+
chainId: chain?.id ?? 1,
11395
type: 'eip1559',
96+
97+
// Set upper-limit-ish stub values. Shouldn't affect the estimate too much as we are
98+
// tweaking dust bytes here (as opposed to long `data` bytes).
99+
// See: https://github.com/ethereum-optimism/optimism/blob/54d02df55523c9e1b4b38ed082c12a42087323a0/packages/contracts-bedrock/src/L2/GasPriceOracle.sol#L242-L248.
100+
gas: args.data ? 300_000n : 21_000n,
101+
maxFeePerGas: parseGwei('5'),
102+
maxPriorityFeePerGas: parseGwei('1'),
103+
nonce: 1,
114104
} as TransactionSerializable)
115105

116106
return readContract(client, {

src/op-stack/actions/estimateTotalGas.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ test('default', async () => {
2525

2626
test('minimal', async () => {
2727
const gas = await estimateTotalGas(optimismClientWithAccount, {})
28-
expect(gas).toBe(54601n)
28+
expect(gas).toBe(54605n)
2929
})
3030

3131
test('args: account', async () => {
@@ -41,7 +41,7 @@ test('args: data', async () => {
4141
...baseTransaction,
4242
data: '0x00000000000000000000000000000000000000000000000004fefa17b7240000',
4343
})
44-
expect(gas).toBe(23744n)
44+
expect(gas).toBe(23760n)
4545
})
4646

4747
test('args: gasPriceOracleAddress', async () => {

0 commit comments

Comments
 (0)