Skip to content

Commit

Permalink
refactor(types): abi error
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm committed Nov 5, 2024
1 parent 4ab0f47 commit ec43171
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 101 deletions.
214 changes: 113 additions & 101 deletions src/AbiError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,104 +8,21 @@ import type * as internal from './internal/abiError.js'
import type * as AbiItem_internal from './internal/abiItem.js'
import type { IsNarrowable, IsNever } from './internal/types.js'

// https://docs.soliditylang.org/en/v0.8.16/control-structures.html#panic-via-assert-and-error-via-require
export const panicReasons = {
1: 'An `assert` condition failed.',
17: 'Arithmetic operation resulted in underflow or overflow.',
18: 'Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).',
33: 'Attempted to convert to an invalid type.',
34: 'Attempted to access a storage byte array that is incorrectly encoded.',
49: 'Performed `.pop()` on an empty array',
50: 'Array index is out of bounds.',
65: 'Allocated too much memory or created an array which is too large.',
81: 'Attempted to call a zero-initialized variable of internal function type.',
} as Record<number, string>

export const solidityError = /*#__PURE__*/ from({
inputs: [
{
name: 'message',
type: 'string',
},
],
name: 'Error',
type: 'error',
})

export const solidityErrorSelector = '0x08c379a0'

export const solidityPanic = /*#__PURE__*/ from({
inputs: [
{
name: 'reason',
type: 'uint8',
},
],
name: 'Panic',
type: 'error',
})

export const solidityPanicSelector = '0x4e487b71'

/** Root type for an {@link ox#AbiItem.AbiItem} with an `error` type. */
export type AbiError = abitype.AbiError & {
hash?: Hex.Hex | undefined
overloads?: readonly AbiError[] | undefined
}

/**
* Extracts an {@link ox#AbiError.AbiError} item from an {@link ox#Abi.Abi}, given a name.
*
* @example
* ```ts twoslash
* import { Abi, AbiError } from 'ox'
*
* const abi = Abi.from([
* 'error Foo(string)',
* 'error Bar(uint256)',
* ])
*
* type Foo = AbiError.FromAbi<typeof abi, 'Foo'>
* // ^?
*
*
*
*
*
*
*
*
* ```
*/
export type FromAbi<
abi extends Abi.Abi,
name extends ExtractNames<abi>,
> = abitype.ExtractAbiError<abi, name>

/**
* Extracts the names of all {@link ox#AbiError.AbiError} items in an {@link ox#Abi.Abi}.
*
* @example
* ```ts twoslash
* import { Abi, AbiError } from 'ox'
*
* const abi = Abi.from([
* 'error Foo(string)',
* 'error Bar(uint256)',
* ])
*
* type names = AbiError.Name<typeof abi>
* // ^?
* ```
*/
export type Name<abi extends Abi.Abi | readonly unknown[] = Abi.Abi> =
abi extends Abi.Abi ? ExtractNames<abi> : string

export type ExtractNames<abi extends Abi.Abi> =
| abitype.ExtractAbiErrorNames<abi>
| 'Panic'
| 'Error'

/** @internal */
export function decode<
const abiError extends AbiError,
as extends 'Object' | 'Array' = 'Array',
>(
abiError: abiError,
data: Hex.Hex,
options?: decode.Options<as> | undefined,
): decode.ReturnType<abiError, as>
/**
* ABI-decodes the provided error input (`inputs`).
*
Expand Down Expand Up @@ -222,14 +139,17 @@ export type ExtractNames<abi extends Abi.Abi> =
* @param options - Decoding options.
* @returns The decoded error.
*/
export function decode<
const abiError extends AbiError,
as extends 'Object' | 'Array' = 'Array',
>(
abiError: abiError | AbiError,
export function decode(
abiError: AbiError,
data: Hex.Hex,
options: decode.Options<as> = {},
): decode.ReturnType<abiError, as> {
options?: decode.Options | undefined,
): unknown | readonly unknown[] | undefined
/** @internal */
export function decode(
abiError: AbiError,
data: Hex.Hex,
options: decode.Options = {},
): decode.ReturnType {
if (Hex.size(data) < 4) throw new AbiItem.InvalidSelectorSizeError({ data })
if (abiError.inputs.length === 0) return undefined

Expand All @@ -246,7 +166,7 @@ export function decode<
}

export declare namespace decode {
type Options<as extends 'Object' | 'Array'> = {
type Options<as extends 'Object' | 'Array' = 'Array'> = {
/**
* Whether the decoded values should be returned as an `Object` or `Array`.
*
Expand Down Expand Up @@ -275,7 +195,7 @@ export declare namespace decode {
? type
: types
: never
: unknown
: unknown | readonly unknown[] | undefined

type ErrorType =
| AbiParameters.decode.ErrorType
Expand Down Expand Up @@ -703,3 +623,95 @@ export declare namespace getSelector {

/* v8 ignore next */
getSelector.parseError = (error: unknown) => error as getSelector.ErrorType

// https://docs.soliditylang.org/en/v0.8.16/control-structures.html#panic-via-assert-and-error-via-require
export const panicReasons = {
1: 'An `assert` condition failed.',
17: 'Arithmetic operation resulted in underflow or overflow.',
18: 'Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).',
33: 'Attempted to convert to an invalid type.',
34: 'Attempted to access a storage byte array that is incorrectly encoded.',
49: 'Performed `.pop()` on an empty array',
50: 'Array index is out of bounds.',
65: 'Allocated too much memory or created an array which is too large.',
81: 'Attempted to call a zero-initialized variable of internal function type.',
} as Record<number, string>

export const solidityError = /*#__PURE__*/ from({
inputs: [
{
name: 'message',
type: 'string',
},
],
name: 'Error',
type: 'error',
})

export const solidityErrorSelector = '0x08c379a0'

export const solidityPanic = /*#__PURE__*/ from({
inputs: [
{
name: 'reason',
type: 'uint8',
},
],
name: 'Panic',
type: 'error',
})

export const solidityPanicSelector = '0x4e487b71'

/**
* Extracts an {@link ox#AbiError.AbiError} item from an {@link ox#Abi.Abi}, given a name.
*
* @example
* ```ts twoslash
* import { Abi, AbiError } from 'ox'
*
* const abi = Abi.from([
* 'error Foo(string)',
* 'error Bar(uint256)',
* ])
*
* type Foo = AbiError.FromAbi<typeof abi, 'Foo'>
* // ^?
*
*
*
*
*
*
*
*
* ```
*/
export type FromAbi<
abi extends Abi.Abi,
name extends ExtractNames<abi>,
> = abitype.ExtractAbiError<abi, name>

/**
* Extracts the names of all {@link ox#AbiError.AbiError} items in an {@link ox#Abi.Abi}.
*
* @example
* ```ts twoslash
* import { Abi, AbiError } from 'ox'
*
* const abi = Abi.from([
* 'error Foo(string)',
* 'error Bar(uint256)',
* ])
*
* type names = AbiError.Name<typeof abi>
* // ^?
* ```
*/
export type Name<abi extends Abi.Abi | readonly unknown[] = Abi.Abi> =
abi extends Abi.Abi ? ExtractNames<abi> : string

export type ExtractNames<abi extends Abi.Abi> =
| abitype.ExtractAbiErrorNames<abi>
| 'Panic'
| 'Error'
46 changes: 46 additions & 0 deletions src/_test/AbiError.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { AbiError } from 'ox'
import { describe, expectTypeOf, test } from 'vitest'

describe('AbiError.decode', () => {
test('behavior: no args', () => {
const error = AbiError.from('error InvalidSignature()')
const decoded = AbiError.decode(error, '0x')
expectTypeOf(decoded).toEqualTypeOf<undefined>()
})

test('behavior: single arg', () => {
const error = AbiError.from('error InvalidSignature(uint8 yParity)')
const decoded = AbiError.decode(error, '0x')
expectTypeOf(decoded).toEqualTypeOf<number>()
})

test('behavior: multiple args', () => {
const error = AbiError.from('error Example(uint r, uint s, uint8 yParity)')
const decoded = AbiError.decode(error, '0x')
expectTypeOf(decoded).toEqualTypeOf<readonly [bigint, bigint, number]>()
})

test('behavior: as = Object', () => {
const error = AbiError.from('error Example(uint r, uint s, uint8 yParity)')
const decoded = AbiError.decode(error, '0x', { as: 'Object' })
expectTypeOf(decoded).toEqualTypeOf<{
r: bigint
s: bigint
yParity: number
}>()
})

test('behavior: as = Object, single arg', () => {
const error = AbiError.from('error Example(uint8 yParity)')
const decoded = AbiError.decode(error, '0x', { as: 'Object' })
expectTypeOf(decoded).toEqualTypeOf<number>()
})

test('not narrowable', () => {
const abiError = {} as AbiError.AbiError
const decoded = AbiError.decode(abiError, '0x')
expectTypeOf(decoded).toEqualTypeOf<
unknown | readonly unknown[] | undefined
>()
})
})

0 comments on commit ec43171

Please sign in to comment.