Skip to content

Commit

Permalink
feat: inferred abi types
Browse files Browse the repository at this point in the history
  • Loading branch information
Raiden1411 committed Jun 14, 2023
1 parent aedd808 commit c397940
Show file tree
Hide file tree
Showing 17 changed files with 156 additions and 48 deletions.
124 changes: 105 additions & 19 deletions src/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ export type Address = ResolvedConfig['AddressType']
// Could use `Range`, but listed out for zero overhead
// rome-ignore format: no formatting
export type MBytes =
| '' | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
| '' | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
| 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19
| 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29
| 30 | 31 | 32
// rome-ignore format: no formatting
export type MBits =
| '' | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72
| 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152
| '' | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72
| 80 | 88 | 96 | 104 | 112 | 120 | 128 | 136 | 144 | 152
| 160 | 168 | 176 | 184 | 192 | 200 | 208 | 216 | 224 | 232
| 240 | 248 | 256

Expand Down Expand Up @@ -92,23 +92,9 @@ export type AbiInternalType =
| ResolvedAbiType
| `address ${string}`
| `contract ${string}`
| `enum ${string}`
| `struct ${string}`
| `enum${string}`
| `struct${string}`

export type InferredAbiParameter = Pretty<
{
type: string
name?: string | undefined
/** Representation used by Solidity compiler */
internalType?: string | undefined
} & (
| { type: string }
| {
type: string
components: readonly InferredAbiParameter[]
}
)
>
export type AbiParameter = Pretty<
{
type: ResolvedAbiType
Expand Down Expand Up @@ -261,3 +247,103 @@ export type TypedData = Pretty<
[_ in TypedDataType]?: never | undefined
}
>

////////////////////////////////////////////////////////////////////////////////////////////////////
// Inferred Abi Types

export type InferredAbiParameter = Pretty<
{
type: string
name?: string | undefined
/** Representation used by Solidity compiler */
internalType?: string | undefined
} & (
| { type: string }
| {
type: 'tuple' | `tuple[${string}]`
components: readonly InferredAbiParameter[]
}
)
>

export type InferredAbiEventParameter = Pretty<
InferredAbiParameter & { indexed?: boolean | undefined }
>

/** Typescript inferred ABI ["function"](https://docs.soliditylang.org/en/latest/abi-spec.html#json) type */
export type InferredAbiFunction = {
type: string
/**
* @deprecated use `pure` or `view` from {@link AbiStateMutability} instead
* @see https://github.com/ethereum/solidity/issues/992
*/
constant?: boolean | undefined
/**
* @deprecated Vyper used to provide gas estimates
* @see https://github.com/vyperlang/vyper/issues/2151
*/
gas?: number | undefined
inputs: readonly InferredAbiParameter[]
name: string
outputs: readonly InferredAbiParameter[]
/**
* @deprecated use `payable` or `nonpayable` from {@link AbiStateMutability} instead
* @see https://github.com/ethereum/solidity/issues/992
*/
payable?: boolean | undefined
stateMutability: string
}

/** Typescript inferred ABI ["constructor"](https://docs.soliditylang.org/en/latest/abi-spec.html#json) type */
export type InferredAbiConstructor = {
type: string
inputs: readonly InferredAbiParameter[]
/**
* @deprecated use `payable` or `nonpayable` from {@link AbiStateMutability} instead
* @see https://github.com/ethereum/solidity/issues/992
*/
payable?: boolean | undefined
stateMutability: string
}

/** Typescript inferred ABI ["fallback"](https://docs.soliditylang.org/en/latest/abi-spec.html#json) type */
export type InferredAbiFallback = {
type: string
inputs?: [] | undefined
/**
* @deprecated use `payable` or `nonpayable` from {@link AbiStateMutability} instead
* @see https://github.com/ethereum/solidity/issues/992
*/
payable?: boolean | undefined
stateMutability: string
}

/** Typescript inferred ABI ["receive"](https://docs.soliditylang.org/en/latest/contracts.html#receive-ether-function) type */
export type InferredAbiReceive = {
type: string
stateMutability: string
}

/** Typescript inferred ABI ["event"](https://docs.soliditylang.org/en/latest/abi-spec.html#events) type */
export type InferredAbiEvent = {
type: string
anonymous?: boolean | undefined
inputs: readonly InferredAbiEventParameter[]
name: string
}

/** Typescript inferred ABI ["error"](https://docs.soliditylang.org/en/latest/abi-spec.html#errors) type */
export type InferredAbiError = {
type: string
inputs: readonly InferredAbiParameter[]
name: string
}

export type InferredAbi = readonly (
| InferredAbiReceive
| InferredAbiFallback
| InferredAbiConstructor
| InferredAbiError
| InferredAbiEvent
| InferredAbiFunction
)[]
4 changes: 2 additions & 2 deletions src/config.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import { assertType, test } from 'vitest'
import type { ResolvedConfig } from './config.js'

// For testing updates to config properties:
// declare module "./config.js" {
// declare module './config.js' {
// export interface Config {
// Strict: true;
// Strict: true
// }
// }

Expand Down
4 changes: 2 additions & 2 deletions src/human-readable/errors/abiParameter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbiItemType, AbiParameter } from '../../abi.js'
import type { AbiItemType, InferredAbiEventParameter } from '../../abi.js'
import { BaseError } from '../../errors.js'
import type { Modifier } from '../types/signatures.js'

Expand Down Expand Up @@ -100,7 +100,7 @@ export class InvalidAbiTypeParameterError extends BaseError {
constructor({
abiParameter,
}: {
abiParameter: AbiParameter & { indexed?: boolean | undefined }
abiParameter: InferredAbiEventParameter
}) {
super('Invalid ABI parameter.', {
details: JSON.stringify(abiParameter, null, 2),
Expand Down
4 changes: 2 additions & 2 deletions src/human-readable/formatAbiParameter.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expectTypeOf, test } from 'vitest'

import type { AbiParameter } from '../abi.js'
import type { InferredAbiParameter } from '../abi.js'

import type { FormatAbiParameter } from './formatAbiParameter.js'
import { formatAbiParameter } from './formatAbiParameter.js'
Expand Down Expand Up @@ -57,7 +57,7 @@ test('formatAbiParameter', () => {
).toEqualTypeOf<'(string)'>()

const param = { type: 'address' }
const param2: AbiParameter = param
const param2: InferredAbiParameter = param
expectTypeOf(formatAbiParameter(param)).toEqualTypeOf<string>()
expectTypeOf(formatAbiParameter(param2)).toEqualTypeOf<string>()
})
10 changes: 7 additions & 3 deletions src/human-readable/formatAbiParameter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { AbiEventParameter, AbiParameter } from '../abi.js'
import type {
AbiParameter,
InferredAbiEventParameter,
InferredAbiParameter,
} from '../abi.js'
import { execTyped } from '../regex.js'
import type { Join } from '../types.js'

Expand All @@ -13,7 +17,7 @@ import type { Join } from '../types.js'
* // ^? type Result = 'address from'
*/
export type FormatAbiParameter<
TAbiParameter extends AbiParameter | AbiEventParameter,
TAbiParameter extends InferredAbiParameter | InferredAbiParameter,
> = TAbiParameter extends {
name?: infer Name extends string
type: `tuple${infer Array}`
Expand Down Expand Up @@ -51,7 +55,7 @@ const tupleRegex = /^tuple(?<array>(\[(\d*)\])*)$/
* // ^? const result: 'address from'
*/
export function formatAbiParameter<
const TAbiParameter extends AbiParameter | AbiEventParameter,
const TAbiParameter extends InferredAbiParameter | InferredAbiEventParameter,
>(abiParameter: TAbiParameter): FormatAbiParameter<TAbiParameter> {
type Result = FormatAbiParameter<TAbiParameter>

Expand Down
4 changes: 2 additions & 2 deletions src/human-readable/formatAbiParameters.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expectTypeOf, test } from 'vitest'

import type { AbiParameter } from '../abi.js'
import type { InferredAbiParameter } from '../abi.js'

import type { FormatAbiParameters } from './formatAbiParameters.js'
import { formatAbiParameters } from './formatAbiParameters.js'
Expand Down Expand Up @@ -78,7 +78,7 @@ test('formatAbiParameter', () => {
).toEqualTypeOf<'(string)'>()

const param = { type: 'address' }
const param2: AbiParameter = param
const param2: InferredAbiParameter = param

expectTypeOf(formatAbiParameters([param])).toEqualTypeOf<string>()

Expand Down
10 changes: 5 additions & 5 deletions src/human-readable/formatAbiParameters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbiEventParameter, AbiParameter } from '../abi.js'
import type { InferredAbiEventParameter, InferredAbiParameter } from '../abi.js'
import type { Join } from '../types.js'
import {
type FormatAbiParameter,
Expand All @@ -20,8 +20,8 @@ import {
*/
export type FormatAbiParameters<
TAbiParameters extends readonly [
AbiParameter | AbiEventParameter,
...(readonly (AbiParameter | AbiEventParameter)[]),
InferredAbiParameter | InferredAbiEventParameter,
...(readonly (InferredAbiParameter | InferredAbiEventParameter)[]),
],
> = Join<
{
Expand All @@ -45,8 +45,8 @@ export type FormatAbiParameters<
*/
export function formatAbiParameters<
const TAbiParameters extends readonly [
AbiParameter | AbiEventParameter,
...(readonly (AbiParameter | AbiEventParameter)[]),
InferredAbiParameter | InferredAbiEventParameter,
...(readonly (InferredAbiParameter | InferredAbiEventParameter)[]),
],
>(abiParameters: TAbiParameters): FormatAbiParameters<TAbiParameters> {
let params = ''
Expand Down
2 changes: 2 additions & 0 deletions src/human-readable/parseAbiParameter.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ test('ParseAbiParameter', () => {
readonly name: 'from'
readonly indexed: true
}>()
// This will fail in strict mode
expectTypeOf<ParseAbiParameter<'address calldata foo'>>().toEqualTypeOf<{
readonly type: 'address'
readonly name: 'foo'
Expand Down Expand Up @@ -91,6 +92,7 @@ test('parseAbiParameter', () => {
).toEqualTypeOf<{ readonly type: 'address' }>()

expectTypeOf(
// This will fail in strict mode
parseAbiParameter(['struct Foo { string name; }', 'Bar']),
).toEqualTypeOf<{ readonly type: 'Bar' }>()

Expand Down
1 change: 1 addition & 0 deletions src/human-readable/parseAbiParameters.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ test('parseAbiParameters', () => {
).toEqualTypeOf<readonly [{ readonly type: 'address' }]>()

expectTypeOf(
// This will fail in strict mode
parseAbiParameters(['struct Foo { string name; }', 'Bar']),
).toEqualTypeOf<readonly [{ readonly type: 'Bar' }]>()

Expand Down
4 changes: 2 additions & 2 deletions src/human-readable/parseAbiParameters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbiParameter } from '../abi.js'
import type { AbiParameter, InferredAbiParameter } from '../abi.js'
import { InvalidAbiParametersError } from '../index.js'
import type { Narrow } from '../narrow.js'
import type { Error, Filter, Flatten, IsEmptyObject } from '../types.js'
Expand Down Expand Up @@ -155,7 +155,7 @@ export function parseAbiParameters<
: never)
),
): ParseAbiParameters<TParams> {
const abiParameters: AbiParameter[] = []
const abiParameters: InferredAbiParameter[] = []
if (typeof params === 'string') {
const parameters = splitParameters(params)
const length = parameters.length
Expand Down
7 changes: 2 additions & 5 deletions src/human-readable/runtime/cache.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbiItemType, AbiParameter } from '../../abi.js'
import type { AbiItemType, InferredAbiEventParameter } from '../../abi.js'

/**
* Gets {@link parameterCache} cache key namespaced by {@link type}. This prevents parameters from being accessible to types that don't allow them (e.g. `string indexed foo` not allowed outside of `type: 'event'`).
Expand All @@ -19,10 +19,7 @@ export function getParameterCacheKey(
*
* **Note: When seeding more parameters, make sure you benchmark performance. The current number is the ideal balance between performance and having an already existing cache.**
*/
export const parameterCache = new Map<
string,
AbiParameter & { indexed?: boolean }
>([
export const parameterCache = new Map<string, InferredAbiEventParameter>([
// Unnamed
['address', { type: 'address' }],
['bool', { type: 'bool' }],
Expand Down
8 changes: 4 additions & 4 deletions src/human-readable/runtime/structs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AbiParameter } from '../../abi.js'
import type { InferredAbiParameter } from '../../abi.js'
import { execTyped, isTupleRegex } from '../../regex.js'
import { UnknownTypeError } from '../errors/abiItem.js'
import { InvalidAbiTypeParameterError } from '../errors/abiParameter.js'
Expand All @@ -24,7 +24,7 @@ export function parseStructs(signatures: readonly string[]) {

const properties = match.properties.split(';')

const components: AbiParameter[] = []
const components: InferredAbiParameter[] = []
const propertiesLength = properties.length
for (let k = 0; k < propertiesLength; k++) {
const property = properties[k]!
Expand Down Expand Up @@ -56,11 +56,11 @@ const typeWithoutTupleRegex =
/^(?<type>[a-zA-Z0-9_]+?)(?<array>(?:\[\d*?\])+?)?$/

function resolveStructs(
abiParameters: readonly (AbiParameter & { indexed?: true })[],
abiParameters: readonly (InferredAbiParameter & { indexed?: true })[],
structs: StructLookup,
ancestors = new Set<string>(),
) {
const components: AbiParameter[] = []
const components: InferredAbiParameter[] = []
const length = abiParameters.length
for (let i = 0; i < length; i++) {
const abiParameter = abiParameters[i]!
Expand Down
5 changes: 5 additions & 0 deletions src/human-readable/types/signatures-test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,15 +214,20 @@ test('ValidateName', () => {
})

test('ValidateType', () => {
// This will fail in strict mode
assertType<ValidateType<'foo'>>(true)
assertType<ValidateType<'address'>>(true)
assertType<ValidateType<'foo', true>>(false)
})

test('ValidateModifier', () => {
// This will fail in strict mode
assertType<ValidateModifier<'calldata', { type: 'address' }>>(true)
// This will fail in strict mode
assertType<ValidateModifier<'storage', { type: 'address' }>>(true)
// This will fail in strict mode
assertType<ValidateModifier<'memory', { type: 'address' }>>(true)
// This will fail in strict mode
assertType<ValidateModifier<'memory', { type: 'Foo' }>>(true)
assertType<ValidateModifier<'memory', { type: 'Foo'; Structs: { Foo: {} } }>>(
true,
Expand Down
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ export type {
AbiStateMutability,
AbiType,
Address,
InferredAbi,
InferredAbiConstructor,
InferredAbiError,
InferredAbiEvent,
InferredAbiEventParameter,
InferredAbiFallback,
InferredAbiFunction,
InferredAbiParameter,
InferredAbiReceive,
SolidityAddress,
SolidityArray,
SolidityArrayWithoutTuple,
Expand Down
Loading

0 comments on commit c397940

Please sign in to comment.