Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: better error messaging & coalesce error messages from nodes #81

Merged
merged 5 commits into from
Feb 22, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/breezy-jobs-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Improve transaction & contract error messaging & coalesce error messages from nodes.
tmm marked this conversation as resolved.
Show resolved Hide resolved
7 changes: 4 additions & 3 deletions src/actions/ens/getEnsAddress.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,10 @@ test('invalid universal resolver address', async () => {
"The contract function \\"resolve\\" reverted with the following reason:
execution reverted

Contract: 0x0000000000000000000000000000000000000000
Function: resolve(bytes name, bytes data)
Arguments: (0x0661776b7765620365746800, 0x3b3b57de52d0f5fbf348925621be297a61b88ec492ebbbdfa9477d82892e2786020ad61c)
Contract Call:
address: 0x0000000000000000000000000000000000000000
function: resolve(bytes name, bytes data)
args: (0x0661776b7765620365746800, 0x3b3b57de52d0f5fbf348925621be297a61b88ec492ebbbdfa9477d82892e2786020ad61c)

Docs: https://viem.sh/docs/contract/readContract
Version: viem@1.0.2"
Expand Down
7 changes: 4 additions & 3 deletions src/actions/ens/getEnsName.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,10 @@ test('invalid universal resolver address', async () => {
"The contract function \\"reverse\\" reverted with the following reason:
execution reverted

Contract: 0x0000000000000000000000000000000000000000
Function: reverse(bytes reverseName)
Arguments: (0x28613063663739383831366434623962393836366235333330656561343661313833383266323531650461646472077265766572736500)
Contract Call:
address: 0x0000000000000000000000000000000000000000
function: reverse(bytes reverseName)
args: (0x28613063663739383831366434623962393836366235333330656561343661313833383266323531650461646472077265766572736500)

Docs: https://viem.sh/docs/contract/readContract
Version: viem@1.0.2"
Expand Down
203 changes: 169 additions & 34 deletions src/actions/public/call.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { expect, test } from 'vitest'
import { describe, expect, test } from 'vitest'

import { accounts, publicClient } from '../../_test'
import { celo } from '../../chains'
import { createPublicClient, http } from '../../clients'
import { numberToHex, parseGwei } from '../../utils'
import { numberToHex, parseEther, parseGwei } from '../../utils'

import { call } from './call'

Expand Down Expand Up @@ -61,38 +61,173 @@ test('args: blockNumber', async () => {
expect(data).toMatchInlineSnapshot('undefined')
})

test('errors: maxFeePerGas less than maxPriorityFeePerGas', async () => {
await expect(
call(publicClient, {
data: `${mintWithParams4bytes}${fourTwenty}`,
from: sourceAccount.address,
to: wagmiContractAddress,
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('22'),
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"\`maxFeePerGas\` cannot be less than \`maxPriorityFeePerGas\`

Version: viem@1.0.2"
`)
})
describe('errors', () => {
test('fee cap too high', async () => {
await expect(() =>
call(publicClient, {
data: `${mintWithParams4bytes}${fourTwenty}`,
from: sourceAccount.address,
to: wagmiContractAddress,
maxFeePerGas: 2n ** 256n - 1n + 1n,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"The fee cap (\`maxFeePerGas\` = 115792089237316195423570985008687907853269984665640564039457584007913.129639936 gwei) cannot be higher than the maximum allowed value (2^256-1).

test('errors: contract revert (contract error)', async () => {
await expect(
call(publicClient, {
data: `${mintWithParams4bytes}${fourTwenty}`,
from: sourceAccount.address,
to: wagmiContractAddress,
}),
).rejects.toThrowError('execution reverted: Token ID is taken')
})
Raw Call Arguments:
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2
data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4
maxFeePerGas: 115792089237316195423570985008687907853269984665640564039457584007913.129639936 gwei

Version: viem@1.0.2"
`)
})

// TODO: Fix anvil error reason
test('gas too low', async () => {
await expect(() =>
call(publicClient, {
data: `${mintWithParams4bytes}${fourTwenty}`,
from: sourceAccount.address,
to: wagmiContractAddress,
gas: 100n,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"An internal error was received.

URL: http://localhost
Request body: {\\"method\\":\\"eth_call\\",\\"params\\":[{\\"from\\":\\"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266\\",\\"data\\":\\"0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4\\",\\"gas\\":\\"0x64\\",\\"to\\":\\"0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2\\"},\\"latest\\"]}

Raw Call Arguments:
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2
data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4
gas: 100

Details: EVM error OutOfGas
Version: viem@1.0.2"
`)
})

// TODO: Fix anvil error (should throw gas too high)
test.skip('gas too high', async () => {
await expect(() =>
call(publicClient, {
from: sourceAccount.address,
to: accounts[0].address,
value: 1n,
gas: 100_000_000_000_000_000n,
}),
).rejects.toThrowErrorMatchingInlineSnapshot()
})

// TODO: Fix anvil – this should fail
test.skip('gas fee is less than block base fee', async () => {
await expect(() =>
call(publicClient, {
from: sourceAccount.address,
to: accounts[0].address,
value: 1n,
maxFeePerGas: 1n,
}),
).rejects.toThrowErrorMatchingInlineSnapshot()
})

// TODO: Fix anvil – this should fail
test.skip('nonce too low', async () => {
await expect(() =>
call(publicClient, {
from: sourceAccount.address,
to: accounts[0].address,
value: 1n,
nonce: 0,
}),
).rejects.toThrowErrorMatchingInlineSnapshot()
})

// TODO: Fix anvil – this should fail with reason
test('insufficient funds', async () => {
await expect(() =>
call(publicClient, {
from: sourceAccount.address,
to: accounts[0].address,
value: parseEther('100000'),
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Execution reverted for an unknown reason.

test('errors: contract revert (insufficient params)', async () => {
await expect(
call(publicClient, {
data: mintWithParams4bytes,
from: sourceAccount.address,
to: wagmiContractAddress,
}),
).rejects.toThrowError('execution reverted')
Raw Call Arguments:
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
to: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
value: 100000 ETH

Details: execution reverted
Version: viem@1.0.2"
`)
})

test('maxFeePerGas less than maxPriorityFeePerGas', async () => {
await expect(
call(publicClient, {
data: `${mintWithParams4bytes}${fourTwenty}`,
from: sourceAccount.address,
to: wagmiContractAddress,
maxFeePerGas: parseGwei('20'),
maxPriorityFeePerGas: parseGwei('22'),
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"The provided tip (\`maxPriorityFeePerGas\` = 22 gwei) cannot be higher than the fee cap (\`maxFeePerGas\` = 20 gwei).

Raw Call Arguments:
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2
data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4
maxFeePerGas: 20 gwei
maxPriorityFeePerGas: 22 gwei

Version: viem@1.0.2"
`)
})

test('contract revert (contract error)', async () => {
await expect(
call(publicClient, {
data: `${mintWithParams4bytes}${fourTwenty}`,
from: sourceAccount.address,
to: wagmiContractAddress,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(
`
"Execution reverted for an unknown reason.

Raw Call Arguments:
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2
data: 0xa0712d6800000000000000000000000000000000000000000000000000000000000001a4

Details: execution reverted: Token ID is taken
Version: viem@1.0.2"
`,
)
})

test('contract revert (insufficient params)', async () => {
await expect(
call(publicClient, {
data: mintWithParams4bytes,
from: sourceAccount.address,
to: wagmiContractAddress,
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"Execution reverted for an unknown reason.

Raw Call Arguments:
from: 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266
to: 0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2
data: 0xa0712d68

Details: execution reverted
Version: viem@1.0.2"
`)
})
})
86 changes: 47 additions & 39 deletions src/actions/public/call.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { PublicClient } from '../../clients'
import { InvalidGasArgumentsError } from '../../errors'
import { BaseError } from '../../errors'
import type {
Address,
BlockTag,
Expand All @@ -9,7 +9,13 @@ import type {
MergeIntersectionProperties,
TransactionRequest,
} from '../../types'
import { extract, Formatted, TransactionRequestFormatter } from '../../utils'
import {
assertRequest,
extract,
Formatted,
getCallError,
TransactionRequestFormatter,
} from '../../utils'
import { format, formatTransactionRequest, numberToHex } from '../../utils'

export type FormattedCall<
Expand Down Expand Up @@ -40,7 +46,9 @@ export type CallResponse = { data: Hex | undefined }

export async function call<TChain extends Chain>(
client: PublicClient<any, TChain>,
{
args: CallArgs<TChain>,
): Promise<CallResponse> {
const {
blockNumber,
blockTag = 'latest',
from,
Expand All @@ -54,42 +62,42 @@ export async function call<TChain extends Chain>(
to,
value,
...rest
}: CallArgs<TChain>,
): Promise<CallResponse> {
if (
maxFeePerGas !== undefined &&
maxPriorityFeePerGas !== undefined &&
maxFeePerGas < maxPriorityFeePerGas
)
throw new InvalidGasArgumentsError()
} = args
try {
assertRequest(args)

const blockNumberHex = blockNumber ? numberToHex(blockNumber) : undefined

const formatter = client.chain?.formatters?.transactionRequest
const request_ = format(
{
from,
accessList,
data,
gas,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
nonce,
to,
value,
// Pick out extra data that might exist on the chain's transaction request type.
...extract(rest, { formatter }),
} as TransactionRequest,
{
formatter: formatter || formatTransactionRequest,
},
)
const blockNumberHex = blockNumber ? numberToHex(blockNumber) : undefined
const formatter = client.chain?.formatters?.transactionRequest
const request_ = format(
{
from,
accessList,
data,
gas,
gasPrice,
maxFeePerGas,
maxPriorityFeePerGas,
nonce,
to,
value,
// Pick out extra data that might exist on the chain's transaction request type.
...extract(rest, { formatter }),
} as TransactionRequest,
{
formatter: formatter || formatTransactionRequest,
},
)

const response = await client.request({
method: 'eth_call',
params: [request_, blockNumberHex || blockTag],
})
if (response === '0x') return { data: undefined }
return { data: response }
const response = await client.request({
method: 'eth_call',
params: [request_, blockNumberHex || blockTag],
})
if (response === '0x') return { data: undefined }
return { data: response }
} catch (err) {
throw getCallError(err as BaseError, {
...args,
chain: client.chain,
})
}
}
Loading