ox
Version:
Ethereum Standard Library
688 lines (630 loc) • 19.3 kB
text/typescript
import type * as abitype from 'abitype'
import type * as Abi from '../Abi.js'
import type * as AbiItem from '../AbiItem.js'
import type * as AbiParameters from '../AbiParameters.js'
import * as Address from '../Address.js'
import * as Errors from '../Errors.js'
import type {
Compute,
IsNever,
IsUnion,
TypeErrorMessage,
UnionToTuple,
} from './types.js'
/** @internal */
export type ExtractArgs<
abi extends Abi.Abi | readonly unknown[] = Abi.Abi,
name extends AbiItem.Name<abi> = AbiItem.Name<abi>,
> =
abitype.AbiParametersToPrimitiveTypes<
AbiItem.FromAbi<abi extends Abi.Abi ? abi : Abi.Abi, name>['inputs'],
'inputs'
> extends infer args
? [args] extends [never]
? readonly unknown[]
: args
: readonly unknown[]
/** @internal */
export type ExtractForArgs<
abi extends Abi.Abi,
name extends AbiItem.Name<abi>,
args extends ExtractArgs<abi, name>,
> =
IsUnion<name> extends true
? {
[key in keyof abi]: abi[key] extends { name: name } ? abi[key] : never
}[number]
: AbiItem.FromAbi<abi, name> extends infer abiItem extends
AbiItem.AbiItem & {
inputs: readonly abitype.AbiParameter[]
}
? IsUnion<abiItem> extends true // narrow overloads using `args` by converting to tuple and filtering out overloads that don't match
? UnionToTuple<abiItem> extends infer abiItems extends
readonly (AbiItem.AbiItem & {
inputs: readonly abitype.AbiParameter[]
})[]
? IsNever<TupleToUnion<abiItems, abi, name, args>> extends true
? Compute<
abiItems[0] & {
readonly overloads: UnionToTuple<
Exclude<abiItems[number], abiItems[0]>
>
}
>
: TupleToUnion<abiItems, abi, name, args> // convert back to union (removes `never` tuple entries: `['foo', never, 'bar'][number]` => `'foo' | 'bar'`)
: never
: abiItem
: never
/** @internal */
export type TupleToUnion<
abiItems extends readonly {
inputs: readonly abitype.AbiParameter[]
}[],
abi extends Abi.Abi,
name extends AbiItem.Name<abi>,
args extends ExtractArgs<abi, name>,
> = {
[k in keyof abiItems]: (
readonly [] extends args
? readonly [] // fallback to `readonly []` if `args` has no value (e.g. `args` property not provided)
: args
) extends abitype.AbiParametersToPrimitiveTypes<
abiItems[k]['inputs'],
'inputs'
>
? abiItems[k]
: never
}[number]
/** @internal */
export type ErrorSignature<
name extends string = string,
parameters extends string = string,
> = `error ${name}(${parameters})`
/** @internal */
export type IsErrorSignature<signature extends string> =
signature extends ErrorSignature<infer name> ? IsName<name> : false
/** @internal */
export type EventSignature<
name extends string = string,
parameters extends string = string,
> = `event ${name}(${parameters})`
/** @internal */
export type IsEventSignature<signature extends string> =
signature extends EventSignature<infer name> ? IsName<name> : false
/** @internal */
export type FunctionSignature<
name extends string = string,
tail extends string = string,
> = `function ${name}(${tail}`
export type IsFunctionSignature<signature> =
signature extends FunctionSignature<infer name>
? IsName<name> extends true
? signature extends ValidFunctionSignatures
? true
: // Check that `Parameters` is not absorbing other types (e.g. `returns`)
signature extends `function ${string}(${infer parameters})`
? parameters extends InvalidFunctionParameters
? false
: true
: false
: false
: false
/** @internal */
export type Scope = 'public' | 'external' // `internal` or `private` functions wouldn't make it to ABI so can ignore
/** @internal */
export type Returns = `returns (${string})` | `returns(${string})`
// Almost all valid function signatures, except `function ${string}(${infer parameters})` since `parameters` can absorb returns
/** @internal */
export type ValidFunctionSignatures =
| `function ${string}()`
// basic
| `function ${string}() ${Returns}`
| `function ${string}() ${abitype.AbiStateMutability}`
| `function ${string}() ${Scope}`
// combinations
| `function ${string}() ${abitype.AbiStateMutability} ${Returns}`
| `function ${string}() ${Scope} ${Returns}`
| `function ${string}() ${Scope} ${abitype.AbiStateMutability}`
| `function ${string}() ${Scope} ${abitype.AbiStateMutability} ${Returns}`
// Parameters
| `function ${string}(${string}) ${Returns}`
| `function ${string}(${string}) ${abitype.AbiStateMutability}`
| `function ${string}(${string}) ${Scope}`
| `function ${string}(${string}) ${abitype.AbiStateMutability} ${Returns}`
| `function ${string}(${string}) ${Scope} ${Returns}`
| `function ${string}(${string}) ${Scope} ${abitype.AbiStateMutability}`
| `function ${string}(${string}) ${Scope} ${abitype.AbiStateMutability} ${Returns}`
/** @internal */
export type StructSignature<
name extends string = string,
properties extends string = string,
> = `struct ${name} {${properties}}`
/** @internal */
export type IsStructSignature<signature extends string> =
signature extends StructSignature<infer name> ? IsName<name> : false
/** @internal */
export type ConstructorSignature<tail extends string = string> =
`constructor(${tail}`
/** @internal */
export type IsConstructorSignature<signature> =
signature extends ConstructorSignature
? signature extends ValidConstructorSignatures
? true
: false
: false
/** @internal */
export type ValidConstructorSignatures =
| `constructor(${string})`
| `constructor(${string}) payable`
/** @internal */
export type FallbackSignature<abiStateMutability extends '' | ' payable' = ''> =
`fallback() external${abiStateMutability}`
/** @internal */
export type ReceiveSignature = 'receive() external payable'
// TODO: Maybe use this for signature validation one day
// https://twitter.com/devanshj__/status/1610423724708343808
/** @internal */
export type IsSignature<type extends string> =
| (IsErrorSignature<type> extends true ? true : never)
| (IsEventSignature<type> extends true ? true : never)
| (IsFunctionSignature<type> extends true ? true : never)
| (IsStructSignature<type> extends true ? true : never)
| (IsConstructorSignature<type> extends true ? true : never)
| (type extends FallbackSignature ? true : never)
| (type extends ReceiveSignature ? true : never) extends infer condition
? [condition] extends [never]
? false
: true
: false
/** @internal */
export type Signature<
string1 extends string,
string2 extends string | unknown = unknown,
> =
IsSignature<string1> extends true
? string1
: string extends string1 // if exactly `string` (not narrowed), then pass through as valid
? string1
: TypeErrorMessage<`Signature "${string1}" is invalid${string2 extends string
? ` at position ${string2}`
: ''}.`>
/** @internal */
export type Signatures<signatures extends readonly string[]> = {
[key in keyof signatures]: Signature<signatures[key], key>
}
/** @internal */
export type IsName<name extends string> = name extends ''
? false
: ValidateName<name> extends name
? true
: false
/** @internal */
export type ValidateName<
name extends string,
checkCharacters extends boolean = false,
> = name extends `${string}${' '}${string}`
? TypeErrorMessage<`Identifier "${name}" cannot contain whitespace.`>
: IsSolidityKeyword<name> extends true
? TypeErrorMessage<`"${name}" is a protected Solidity keyword.`>
: name extends `${number}`
? TypeErrorMessage<`Identifier "${name}" cannot be a number string.`>
: name extends `${number}${string}`
? TypeErrorMessage<`Identifier "${name}" cannot start with a number.`>
: checkCharacters extends true
? IsValidCharacter<name> extends true
? name
: TypeErrorMessage<`"${name}" contains invalid character.`>
: name
/** @internal */
export type IsSolidityKeyword<type extends string> =
type extends SolidityKeywords ? true : false
/** @internal */
export type SolidityKeywords =
| 'after'
| 'alias'
| 'anonymous'
| 'apply'
| 'auto'
| 'byte'
| 'calldata'
| 'case'
| 'catch'
| 'constant'
| 'copyof'
| 'default'
| 'defined'
| 'error'
| 'event'
| 'external'
| 'false'
| 'final'
| 'function'
| 'immutable'
| 'implements'
| 'in'
| 'indexed'
| 'inline'
| 'internal'
| 'let'
| 'mapping'
| 'match'
| 'memory'
| 'mutable'
| 'null'
| 'of'
| 'override'
| 'partial'
| 'private'
| 'promise'
| 'public'
| 'pure'
| 'reference'
| 'relocatable'
| 'return'
| 'returns'
| 'sizeof'
| 'static'
| 'storage'
| 'struct'
| 'super'
| 'supports'
| 'switch'
| 'this'
| 'true'
| 'try'
| 'typedef'
| 'typeof'
| 'var'
| 'view'
| 'virtual'
| `address${`[${string}]` | ''}`
| `bool${`[${string}]` | ''}`
| `string${`[${string}]` | ''}`
| `tuple${`[${string}]` | ''}`
| `bytes${number | ''}${`[${string}]` | ''}`
| `${'u' | ''}int${number | ''}${`[${string}]` | ''}`
/** @internal */
export type IsValidCharacter<character extends string> =
character extends `${ValidCharacters}${infer tail}`
? tail extends ''
? true
: IsValidCharacter<tail>
: false
// biome-ignore format: no formatting
/** @internal */
export type ValidCharacters =
// uppercase letters
| 'A'
| 'B'
| 'C'
| 'D'
| 'E'
| 'F'
| 'G'
| 'H'
| 'I'
| 'J'
| 'K'
| 'L'
| 'M'
| 'N'
| 'O'
| 'P'
| 'Q'
| 'R'
| 'S'
| 'T'
| 'U'
| 'V'
| 'W'
| 'X'
| 'Y'
| 'Z'
// lowercase letters
| 'a'
| 'b'
| 'c'
| 'd'
| 'e'
| 'f'
| 'g'
| 'h'
| 'i'
| 'j'
| 'k'
| 'l'
| 'm'
| 'n'
| 'o'
| 'p'
| 'q'
| 'r'
| 's'
| 't'
| 'u'
| 'v'
| 'w'
| 'x'
| 'y'
| 'z'
// numbers
| '0'
| '1'
| '2'
| '3'
| '4'
| '5'
| '6'
| '7'
| '8'
| '9'
// special characters
| '_'
| '$'
// Template string inference can absorb `returns`:
// type Result = `function foo(string) return s (uint256)` extends `function ${string}(${infer Parameters})` ? Parameters : never
// // ^? type Result = "string ) return s (uint256"
// So we need to validate against `returns` keyword with all combinations of whitespace
/** @internal */
export type InvalidFunctionParameters =
| `${string}${MangledReturns} (${string}`
| `${string}) ${MangledReturns}${string}`
| `${string})${string}${MangledReturns}${string}(${string}`
// r_e_t_u_r_n_s
/** @internal */
export type MangledReturns =
// Single
| `r${string}eturns`
| `re${string}turns`
| `ret${string}urns`
| `retu${string}rns`
| `retur${string}ns`
| `return${string}s`
// Double
// `r_e*`
| `r${string}e${string}turns`
| `r${string}et${string}urns`
| `r${string}etu${string}rns`
| `r${string}etur${string}ns`
| `r${string}eturn${string}s`
// `re_t*`
| `re${string}t${string}urns`
| `re${string}tu${string}rns`
| `re${string}tur${string}ns`
| `re${string}turn${string}s`
// `ret_u*`
| `ret${string}u${string}rns`
| `ret${string}ur${string}ns`
| `ret${string}urn${string}s`
// `retu_r*`
| `retu${string}r${string}ns`
| `retu${string}rn${string}s`
// `retur_n*`
| `retur${string}n${string}s`
// Triple
// `r_e_t*`
| `r${string}e${string}t${string}urns`
| `r${string}e${string}tu${string}rns`
| `r${string}e${string}tur${string}ns`
| `r${string}e${string}turn${string}s`
// `re_t_u*`
| `re${string}t${string}u${string}rns`
| `re${string}t${string}ur${string}ns`
| `re${string}t${string}urn${string}s`
// `ret_u_r*`
| `ret${string}u${string}r${string}ns`
| `ret${string}u${string}rn${string}s`
// `retu_r_n*`
| `retu${string}r${string}n${string}s`
// Quadruple
// `r_e_t_u*`
| `r${string}e${string}t${string}u${string}rns`
| `r${string}e${string}t${string}ur${string}ns`
| `r${string}e${string}t${string}urn${string}s`
// `re_t_u_r*`
| `re${string}t${string}u${string}r${string}ns`
| `re${string}t${string}u${string}rn${string}s`
// `ret_u_r_n*`
| `ret${string}u${string}r${string}n${string}s`
// Quintuple
// `r_e_t_u_r*`
| `r${string}e${string}t${string}u${string}r${string}ns`
| `r${string}e${string}t${string}u${string}rn${string}s`
// `re_t_u_r_n*`
| `re${string}t${string}u${string}r${string}n${string}s`
// Sextuple
// `r_e_t_u_r_n_s`
| `r${string}e${string}t${string}u${string}r${string}n${string}s`
/** @internal */
export type Widen<type> =
| ([unknown] extends [type] ? unknown : never)
| (type extends Function ? type : never)
| (type extends abitype.ResolvedRegister['bigIntType'] ? bigint : never)
| (type extends boolean ? boolean : never)
| (type extends abitype.ResolvedRegister['intType'] ? number : never)
| (type extends string
? type extends abitype.ResolvedRegister['addressType']
? abitype.ResolvedRegister['addressType']
: type extends abitype.ResolvedRegister['bytesType']['inputs']
? abitype.ResolvedRegister['bytesType']
: string
: never)
| (type extends readonly [] ? readonly [] : never)
| (type extends Record<string, unknown>
? { [K in keyof type]: Widen<type[K]> }
: never)
| (type extends { length: number }
? {
[K in keyof type]: Widen<type[K]>
} extends infer Val extends readonly unknown[]
? readonly [...Val]
: never
: never)
/** @internal */
export function normalizeSignature(signature: string): string {
let active = true
let current = ''
let level = 0
let result = ''
let valid = false
for (let i = 0; i < signature.length; i++) {
const char = signature[i]!
// If the character is a separator, we want to reactivate.
if (['(', ')', ','].includes(char)) active = true
// If the character is a "level" token, we want to increment/decrement.
if (char === '(') level++
if (char === ')') level--
// If we aren't active, we don't want to mutate the result.
if (!active) continue
// If level === 0, we are at the definition level.
if (level === 0) {
if (char === ' ' && ['event', 'function', 'error', ''].includes(result))
result = ''
else {
result += char
// If we are at the end of the definition, we must be finished.
if (char === ')') {
valid = true
break
}
}
continue
}
// Ignore spaces
if (char === ' ') {
// If the previous character is a separator, and the current section isn't empty, we want to deactivate.
if (signature[i - 1] !== ',' && current !== ',' && current !== ',(') {
current = ''
active = false
}
continue
}
result += char
current += char
}
if (!valid) throw new Errors.BaseError('Unable to normalize signature.')
return result
}
/** @internal */
export declare namespace normalizeSignature {
export type ErrorType = Errors.BaseError | Errors.GlobalErrorType
}
/**
* `(u)int<M>` regex: (un)signed integer type of `M` bits, `0 < M <= 256`,
* `M % 8 == 0`. Hoisted to module scope; the prior literal at the call
* site was recompiled per overload-resolution iteration.
*
* @internal
*/
const integerTypeRegex =
/^u?int(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)?$/
/**
* `bytes<M>` regex: binary type of `M` bytes, `0 < M <= 32`.
*
* @internal
*/
const fixedBytesTypeRegex = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/
/**
* Fixed-length (`<type>[M]`) / dynamic (`<type>[]`) array regex.
*
* @internal
*/
const arrayTypeRegex = /[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/
/**
* Strips a single trailing `[...]` array suffix from a type.
*
* @internal
*/
const arraySuffixStripRegex = /(\[[0-9]{0,}\])$/
/** @internal */
export function isArgOfType(
arg: unknown,
abiParameter: AbiParameters.Parameter,
): boolean {
const argType = typeof arg
const abiParameterType = abiParameter.type
switch (abiParameterType) {
case 'address':
return Address.validate(arg as Address.Address, { strict: false })
case 'bool':
return argType === 'boolean'
case 'function':
return argType === 'string'
case 'string':
return argType === 'string'
default: {
if (abiParameterType === 'tuple' && 'components' in abiParameter)
return Object.values(abiParameter.components).every(
(component, index) => {
return isArgOfType(
Object.values(arg as unknown[] | Record<string, unknown>)[index],
component as AbiParameters.Parameter,
)
},
)
if (integerTypeRegex.test(abiParameterType))
return argType === 'number' || argType === 'bigint'
if (fixedBytesTypeRegex.test(abiParameterType))
return argType === 'string' || arg instanceof Uint8Array
if (arrayTypeRegex.test(abiParameterType)) {
return (
Array.isArray(arg) &&
arg.every((x: unknown) =>
isArgOfType(x, {
...abiParameter,
// Pop off `[]` or `[M]` from end of type
type: abiParameterType.replace(arraySuffixStripRegex, ''),
} as AbiParameters.Parameter),
)
)
}
return false
}
}
}
/** @internal */
export function getAmbiguousTypes(
sourceParameters: readonly AbiParameters.Parameter[],
targetParameters: readonly AbiParameters.Parameter[],
args: ExtractArgs,
): AbiParameters.Parameter['type'][] | undefined {
// Indexed `for` loop instead of `for...in` (which iterates inherited
// enumerable keys and is materially slower on arrays).
for (
let parameterIndex = 0;
parameterIndex < sourceParameters.length;
parameterIndex++
) {
const sourceParameter = sourceParameters[parameterIndex]!
const targetParameter = targetParameters[parameterIndex]!
if (
sourceParameter.type === 'tuple' &&
targetParameter.type === 'tuple' &&
'components' in sourceParameter &&
'components' in targetParameter
)
return getAmbiguousTypes(
sourceParameter.components,
targetParameter.components,
(args as any)[parameterIndex],
)
const sourceType = sourceParameter.type
const targetType = targetParameter.type
const arg = args[parameterIndex] as Address.Address
let ambiguous = false
if (
(sourceType === 'address' && targetType === 'bytes20') ||
(sourceType === 'bytes20' && targetType === 'address')
)
ambiguous = true
else if (
(sourceType === 'address' && targetType === 'string') ||
(sourceType === 'string' && targetType === 'address') ||
(sourceType === 'address' && targetType === 'bytes') ||
(sourceType === 'bytes' && targetType === 'address')
)
ambiguous = Address.validate(arg, { strict: false })
if (ambiguous) return [sourceType, targetType]
}
return
}