ox
Version:
Ethereum Standard Library
98 lines (85 loc) • 3.27 kB
text/typescript
import type { AbiParameter } from 'abitype'
import { execTyped, isTupleRegex } from '../regex.js'
import { UnknownTypeError } from '../errors.js'
import { InvalidAbiTypeParameterError } from '../errors.js'
import {
InvalidSignatureError,
InvalidStructSignatureError,
} from '../errors.js'
import { CircularReferenceError } from '../errors.js'
import type { StructLookup } from '../types/structs.js'
import { execStructSignature, isStructSignature } from './signatures.js'
import { isSolidityType, parseAbiParameter } from './utils.js'
export function parseStructs(signatures: readonly string[]) {
// Create "shallow" version of each struct (and filter out non-structs or invalid structs)
const shallowStructs: StructLookup = {}
const signaturesLength = signatures.length
for (let i = 0; i < signaturesLength; i++) {
const signature = signatures[i]!
if (!isStructSignature(signature)) continue
const match = execStructSignature(signature)
if (!match) throw new InvalidSignatureError({ signature, type: 'struct' })
const properties = match.properties.split(';')
const components: AbiParameter[] = []
const propertiesLength = properties.length
for (let k = 0; k < propertiesLength; k++) {
const property = properties[k]!
const trimmed = property.trim()
if (!trimmed) continue
const abiParameter = parseAbiParameter(trimmed, {
type: 'struct',
})
components.push(abiParameter)
}
if (!components.length) throw new InvalidStructSignatureError({ signature })
shallowStructs[match.name] = components
}
// Resolve nested structs inside each parameter
const resolvedStructs: StructLookup = {}
const entries = Object.entries(shallowStructs)
const entriesLength = entries.length
for (let i = 0; i < entriesLength; i++) {
const [name, parameters] = entries[i]!
resolvedStructs[name] = resolveStructs(parameters, shallowStructs)
}
return resolvedStructs
}
const typeWithoutTupleRegex =
/^(?<type>[a-zA-Z$_][a-zA-Z0-9$_]*)(?<array>(?:\[\d*?\])+?)?$/
function resolveStructs(
abiParameters: readonly (AbiParameter & { indexed?: true })[] = [],
structs: StructLookup = {},
ancestors = new Set<string>(),
) {
const components: AbiParameter[] = []
const length = abiParameters.length
for (let i = 0; i < length; i++) {
const abiParameter = abiParameters[i]!
const isTuple = isTupleRegex.test(abiParameter.type)
if (isTuple) components.push(abiParameter)
else {
const match = execTyped<{ array?: string; type: string }>(
typeWithoutTupleRegex,
abiParameter.type,
)
if (!match?.type) throw new InvalidAbiTypeParameterError({ abiParameter })
const { array, type } = match
if (type in structs) {
if (ancestors.has(type)) throw new CircularReferenceError({ type })
components.push({
...abiParameter,
type: `tuple${array ?? ''}`,
components: resolveStructs(
structs[type],
structs,
new Set([...ancestors, type]),
),
})
} else {
if (isSolidityType(type)) components.push(abiParameter)
else throw new UnknownTypeError({ type })
}
}
}
return components
}