Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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/abi-error-decode-overloads.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch
---

Fixed `AbiError.decode` to resolve overloaded errors by the selector in `data` instead of always decoding with the first matching overload.
55 changes: 33 additions & 22 deletions src/core/AbiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -210,14 +210,17 @@ export function decode(
}
return parameters as [AbiError, Hex.Hex, decode.Options | undefined]
})()
const { overloads } = abiError as AbiError

if (Hex.size(data) < 4) throw new AbiItem.InvalidSelectorSizeError({ data })
if (abiError.inputs?.length === 0) return undefined

const values = AbiParameters.decode(
abiError.inputs,
Hex.slice(data, 4),
options,
)
const item = overloads
? (fromAbi([abiError, ...overloads], data as never) as AbiError)
: abiError

if (item.inputs?.length === 0) return undefined

const values = AbiParameters.decode(item.inputs, Hex.slice(data, 4), options)
if (values && Object.keys(values).length === 1) {
if (Array.isArray(values)) return values[0]
return Object.values(values)[0]
Expand All @@ -239,22 +242,30 @@ export declare namespace decode {
abiError extends AbiError = AbiError,
as extends 'Object' | 'Array' = 'Array',
> = IsNarrowable<abiError, AbiError> extends true
? abiError['inputs'] extends readonly []
? undefined
: abiError['inputs'] extends readonly [
infer type extends abitype.AbiParameter,
]
? abitype.AbiParameterToPrimitiveType<type>
: AbiParameters.decode.ReturnType<
abiError['inputs'],
as
> extends infer types
? types extends readonly []
? undefined
: types extends readonly [infer type]
? type
: types
: never
? // distribute over the error and its overloads
| abiError
| (abiError['overloads'] extends readonly AbiError[]
? abiError['overloads'][number]
: never) extends infer item extends AbiError
? item extends AbiError
? item['inputs'] extends readonly []
? undefined
: item['inputs'] extends readonly [
infer type extends abitype.AbiParameter,
]
? abitype.AbiParameterToPrimitiveType<type>
: AbiParameters.decode.ReturnType<
item['inputs'],
as
> extends infer types
? types extends readonly []
? undefined
: types extends readonly [infer type]
? type
: types
: never
: never
: never
: unknown | readonly unknown[] | undefined

type ErrorType =
Expand Down
12 changes: 11 additions & 1 deletion src/core/_test/AbiError.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AbiError } from 'ox'
import { Abi, AbiError } from 'ox'
import { describe, expectTypeOf, test } from 'vitest'

describe('AbiError.decode', () => {
Expand Down Expand Up @@ -36,6 +36,16 @@ describe('AbiError.decode', () => {
expectTypeOf(decoded).toEqualTypeOf<number>()
})

test('behavior: with overloads', () => {
const abi = Abi.from([
'error Example()',
'error Example(uint256 amount)',
'error Example(string reason)',
])
const decoded = AbiError.decode(abi, 'Example', '0x')
expectTypeOf(decoded).toEqualTypeOf<bigint | string | undefined>()
})

test('not narrowable', () => {
const abiError = {} as AbiError.AbiError
const decoded = AbiError.decode(abiError, '0x')
Expand Down
31 changes: 31 additions & 0 deletions src/core/_test/AbiError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,37 @@ describe('decode', () => {
expect(decoded).toEqual(1)
})

test('behavior: with overloads, resolved by name', () => {
const abi = Abi.from([
'error Example()',
'error Example(uint256 amount)',
'error Example(string reason)',
])
expect(
AbiError.decode(
abi,
'Example',
AbiError.encode(AbiError.from('error Example(uint256 amount)'), [5n]),
),
).toEqual(5n)
expect(
AbiError.decode(
abi,
'Example',
AbiError.encode(AbiError.from('error Example(string reason)'), [
'denied',
]),
),
).toEqual('denied')
expect(
AbiError.decode(
abi,
'Example',
AbiError.encode(AbiError.from('error Example()')),
),
).toEqual(undefined)
})

test('error: invalid data', () => {
const error = AbiError.from('error Example(uint256 a)')
expect(() =>
Expand Down
Loading