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/zero-input-overload-decode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ox": patch
---

Fixed `AbiFunction.decodeData` returning `undefined` when a function name has a zero-input overload and the calldata targets another overload, by keeping the other overloads on items `fromAbi` resolves without `args`.
12 changes: 7 additions & 5 deletions src/core/AbiFunction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,12 @@ export function decodeData(
const { overloads } = abiFunction

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

const item = overloads
? fromAbi([abiFunction, ...overloads], data as never)
: abiFunction

if (item.inputs.length === 0) return undefined
if (Hex.size(data) <= 4) return undefined
return AbiParameters.decode(item.inputs, Hex.slice(data, 4))
}
Expand All @@ -172,15 +172,17 @@ export declare namespace decodeData {
abiFunction,
AbiFunction
> extends true
? abiFunction['inputs'] extends readonly []
? undefined
:
| AbiParameters_internal.ToPrimitiveTypes<abiFunction['inputs']>
? abiFunction extends AbiFunction
?
| (abiFunction['inputs'] extends readonly []
? undefined
: AbiParameters_internal.ToPrimitiveTypes<abiFunction['inputs']>)
| (abiFunction['overloads'] extends readonly AbiFunction[]
? AbiParameters_internal.ToPrimitiveTypes<
abiFunction['overloads'][number]['inputs']
>
: never)
: never
: unknown

type ErrorType =
Expand Down
15 changes: 8 additions & 7 deletions src/core/AbiItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -359,14 +359,14 @@ export function fromAbi<
} as never

let matchedAbiItem: AbiItem | undefined
// Without `args`, a zero-input overload is the preferred pick, but the other
// overloads are still attached so callers can resolve by selector.
let zeroArgAbiItem: AbiItem | undefined
for (const abiItem of abiItems) {
if (!('inputs' in abiItem)) continue
if (!args || args.length === 0) {
if (!abiItem.inputs || abiItem.inputs.length === 0)
return {
...abiItem,
...(prepare ? { hash: getSignatureHash(abiItem) } : {}),
} as never
if (!zeroArgAbiItem && (!abiItem.inputs || abiItem.inputs.length === 0))
zeroArgAbiItem = abiItem
continue
}
if (!abiItem.inputs) continue
Expand Down Expand Up @@ -408,8 +408,9 @@ export function fromAbi<

const abiItem = (() => {
if (matchedAbiItem) return matchedAbiItem
const [abiItem, ...overloads] = abiItems
return { ...abiItem!, overloads }
const chosen = zeroArgAbiItem ?? abiItems[0]
const overloads = abiItems.filter((item) => item !== chosen)
return { ...chosen!, overloads }
})()

if (!abiItem) throw new NotFoundError({ name: name as string })
Expand Down
84 changes: 63 additions & 21 deletions src/core/_test/AbiError.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -667,26 +667,48 @@ describe('fromAbi', () => {
const abi = Abi.from(['error Bar()', 'error Foo()', 'error Foo(uint256)'])
const item = AbiError.fromAbi(abi, 'Foo')
expect(item).toMatchInlineSnapshot(`
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"type": "error",
}
`)
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"overloads": [
{
"inputs": [
{
"type": "uint256",
},
],
"name": "Foo",
"type": "error",
},
],
"type": "error",
}
`)
})

test('overloads: no args', () => {
const abi = Abi.from(['error Bar()', 'error Foo(uint256)', 'error Foo()'])
const item = AbiError.fromAbi(abi, 'Foo')
expect(item).toMatchInlineSnapshot(`
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"type": "error",
}
`)
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"overloads": [
{
"inputs": [
{
"type": "uint256",
},
],
"name": "Foo",
"type": "error",
},
],
"type": "error",
}
`)
})

test('behavior: overloads: different types', () => {
Expand All @@ -697,13 +719,33 @@ describe('fromAbi', () => {
])
const item = AbiError.fromAbi(abi, 'Mint')
expect(item).toMatchInlineSnapshot(`
{
"hash": "0x34c73884fbbb790762253ae313e57da96c00670344647f0cb8d41ee92b9f1971",
"inputs": [],
"name": "Mint",
"type": "error",
}
`)
{
"hash": "0x34c73884fbbb790762253ae313e57da96c00670344647f0cb8d41ee92b9f1971",
"inputs": [],
"name": "Mint",
"overloads": [
{
"inputs": [
{
"type": "uint256",
},
],
"name": "Mint",
"type": "error",
},
{
"inputs": [
{
"type": "string",
},
],
"name": "Mint",
"type": "error",
},
],
"type": "error",
}
`)

const item_2 = AbiError.fromAbi(abi, 'Mint', {
args: [420n],
Expand Down
9 changes: 9 additions & 0 deletions src/core/_test/AbiEvent.snap-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,15 @@ describe('fromAbi', () => {
readonly name: "Foo"
readonly type: "event"
readonly inputs: readonly []
readonly overloads: [
{
readonly name: "Foo"
readonly type: "event"
readonly inputs: readonly [
{ readonly type: "uint256" }
]
}
]
}`)
})

Expand Down
84 changes: 63 additions & 21 deletions src/core/_test/AbiEvent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1476,26 +1476,48 @@ describe('fromAbi', () => {
const abi = Abi.from(['event Bar()', 'event Foo()', 'event Foo(uint256)'])
const item = AbiEvent.fromAbi(abi, 'Foo')
expect(item).toMatchInlineSnapshot(`
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"type": "event",
}
`)
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"overloads": [
{
"inputs": [
{
"type": "uint256",
},
],
"name": "Foo",
"type": "event",
},
],
"type": "event",
}
`)
})

test('overloads: no args', () => {
const abi = Abi.from(['event Bar()', 'event Foo(uint256)', 'event Foo()'])
const item = AbiEvent.fromAbi(abi, 'Foo')
expect(item).toMatchInlineSnapshot(`
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"type": "event",
}
`)
{
"hash": "0xbfb4ebcfff8f360b39de1de85df1edc256d63337b743120bf6e2e2144b973d38",
"inputs": [],
"name": "Foo",
"overloads": [
{
"inputs": [
{
"type": "uint256",
},
],
"name": "Foo",
"type": "event",
},
],
"type": "event",
}
`)
})

test('behavior: overloads: different types', () => {
Expand All @@ -1506,13 +1528,33 @@ describe('fromAbi', () => {
])
const item = AbiEvent.fromAbi(abi, 'Mint')
expect(item).toMatchInlineSnapshot(`
{
"hash": "0x34c73884fbbb790762253ae313e57da96c00670344647f0cb8d41ee92b9f1971",
"inputs": [],
"name": "Mint",
"type": "event",
}
`)
{
"hash": "0x34c73884fbbb790762253ae313e57da96c00670344647f0cb8d41ee92b9f1971",
"inputs": [],
"name": "Mint",
"overloads": [
{
"inputs": [
{
"type": "uint256",
},
],
"name": "Mint",
"type": "event",
},
{
"inputs": [
{
"type": "string",
},
],
"name": "Mint",
"type": "event",
},
],
"type": "event",
}
`)

const item_2 = AbiEvent.fromAbi(abi, 'Mint', {
args: [420n],
Expand Down
25 changes: 25 additions & 0 deletions src/core/_test/AbiFunction.snap-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,20 @@ describe('decodeData', () => {
),
).type.toString.snap('readonly [bigint] | readonly [`0x${string}`]')
})

test('behavior: with overloads, one with zero inputs', () => {
const abi = Abi.from([
'function deposit()',
'function deposit(uint256 assets)',
])
attest(
AbiFunction.decodeData(
abi,
'deposit',
'0xb6b55f25000000000000000000000000000000000000000000000000000000000000007b',
),
).type.toString.snap('readonly [bigint] | undefined')
})
})

describe('decodeResult', () => {
Expand Down Expand Up @@ -208,6 +222,17 @@ describe('fromAbi', () => {
readonly stateMutability: "nonpayable"
readonly inputs: readonly []
readonly outputs: readonly []
readonly overloads: [
{
readonly name: "foo"
readonly type: "function"
readonly stateMutability: "nonpayable"
readonly inputs: readonly [
{ readonly type: "uint256" }
]
readonly outputs: readonly []
}
]
}`)
})

Expand Down
Loading
Loading