viem
Version:
205 lines (188 loc) • 6.25 kB
text/typescript
import type { Abi, AbiParameter } from 'abitype'
import {
AbiDecodingDataSizeTooSmallError,
type AbiDecodingDataSizeTooSmallErrorType,
AbiEventSignatureEmptyTopicsError,
type AbiEventSignatureEmptyTopicsErrorType,
AbiEventSignatureNotFoundError,
type AbiEventSignatureNotFoundErrorType,
DecodeLogDataMismatch,
type DecodeLogDataMismatchErrorType,
DecodeLogTopicsMismatch,
type DecodeLogTopicsMismatchErrorType,
} from '../../errors/abi.js'
import type { ErrorType } from '../../errors/utils.js'
import type {
ContractEventArgsFromTopics,
ContractEventName,
EventDefinition,
} from '../../types/contract.js'
import type { Hex } from '../../types/misc.js'
import type {
IsNarrowable,
Prettify,
UnionEvaluate,
} from '../../types/utils.js'
import { size } from '../data/size.js'
import {
type ToEventSelectorErrorType,
toEventSelector,
} from '../hash/toEventSelector.js'
import { PositionOutOfBoundsError } from '../../errors/cursor.js'
import {
type DecodeAbiParametersErrorType,
decodeAbiParameters,
} from './decodeAbiParameters.js'
import { type FormatAbiItemErrorType, formatAbiItem } from './formatAbiItem.js'
export type DecodeEventLogParameters<
abi extends Abi | readonly unknown[] = Abi,
eventName extends ContractEventName<abi> | undefined = ContractEventName<abi>,
topics extends Hex[] = Hex[],
data extends Hex | undefined = undefined,
strict extends boolean = true,
> = {
abi: abi
data?: data | undefined
eventName?: eventName | ContractEventName<abi> | undefined
strict?: strict | boolean | undefined
topics: [signature: Hex, ...args: topics] | []
}
export type DecodeEventLogReturnType<
abi extends Abi | readonly unknown[] = Abi,
eventName extends ContractEventName<abi> | undefined = ContractEventName<abi>,
topics extends Hex[] = Hex[],
data extends Hex | undefined = undefined,
strict extends boolean = true,
///
allEventNames extends
ContractEventName<abi> = eventName extends ContractEventName<abi>
? eventName
: ContractEventName<abi>,
> = IsNarrowable<abi, Abi> extends true
? {
[name in allEventNames]: Prettify<
{
eventName: name
} & UnionEvaluate<
ContractEventArgsFromTopics<abi, name, strict> extends infer allArgs
? topics extends readonly []
? data extends undefined
? { args?: undefined }
: { args?: allArgs | undefined }
: { args: allArgs }
: never
>
>
}[allEventNames]
: {
eventName: eventName
args: readonly unknown[] | undefined
}
export type DecodeEventLogErrorType =
| AbiDecodingDataSizeTooSmallErrorType
| AbiEventSignatureEmptyTopicsErrorType
| AbiEventSignatureNotFoundErrorType
| DecodeAbiParametersErrorType
| DecodeLogTopicsMismatchErrorType
| DecodeLogDataMismatchErrorType
| FormatAbiItemErrorType
| ToEventSelectorErrorType
| ErrorType
const docsPath = '/docs/contract/decodeEventLog'
export function decodeEventLog<
const abi extends Abi | readonly unknown[],
eventName extends ContractEventName<abi> | undefined = undefined,
topics extends Hex[] = Hex[],
data extends Hex | undefined = undefined,
strict extends boolean = true,
>(
parameters: DecodeEventLogParameters<abi, eventName, topics, data, strict>,
): DecodeEventLogReturnType<abi, eventName, topics, data, strict> {
const {
abi,
data,
strict: strict_,
topics,
} = parameters as DecodeEventLogParameters
const strict = strict_ ?? true
const [signature, ...argTopics] = topics
if (!signature) throw new AbiEventSignatureEmptyTopicsError({ docsPath })
const abiItem = (() => {
if (abi.length === 1) return abi[0]
return abi.find(
(x) =>
x.type === 'event' &&
signature === toEventSelector(formatAbiItem(x) as EventDefinition),
)
})()
if (!(abiItem && 'name' in abiItem) || abiItem.type !== 'event')
throw new AbiEventSignatureNotFoundError(signature, { docsPath })
const { name, inputs } = abiItem
const isUnnamed = inputs?.some((x) => !('name' in x && x.name))
let args: any = isUnnamed ? [] : {}
// Decode topics (indexed args).
const indexedInputs = inputs.filter((x) => 'indexed' in x && x.indexed)
for (let i = 0; i < indexedInputs.length; i++) {
const param = indexedInputs[i]
const topic = argTopics[i]
if (!topic)
throw new DecodeLogTopicsMismatch({
abiItem,
param: param as AbiParameter & { indexed: boolean },
})
args[isUnnamed ? i : param.name || i] = decodeTopic({ param, value: topic })
}
// Decode data (non-indexed args).
const nonIndexedInputs = inputs.filter((x) => !('indexed' in x && x.indexed))
if (nonIndexedInputs.length > 0) {
if (data && data !== '0x') {
try {
const decodedData = decodeAbiParameters(nonIndexedInputs, data)
if (decodedData) {
if (isUnnamed) args = [...args, ...decodedData]
else {
for (let i = 0; i < nonIndexedInputs.length; i++) {
args[nonIndexedInputs[i].name!] = decodedData[i]
}
}
}
} catch (err) {
if (strict) {
if (
err instanceof AbiDecodingDataSizeTooSmallError ||
err instanceof PositionOutOfBoundsError
)
throw new DecodeLogDataMismatch({
abiItem,
data: data,
params: nonIndexedInputs,
size: size(data),
})
throw err
}
}
} else if (strict) {
throw new DecodeLogDataMismatch({
abiItem,
data: '0x',
params: nonIndexedInputs,
size: 0,
})
}
}
return {
eventName: name,
args: Object.values(args).length > 0 ? args : undefined,
} as unknown as DecodeEventLogReturnType<abi, eventName, topics, data, strict>
}
function decodeTopic({ param, value }: { param: AbiParameter; value: Hex }) {
if (
param.type === 'string' ||
param.type === 'bytes' ||
param.type === 'tuple' ||
param.type.match(/^(.*)\[(\d+)?\]$/)
)
return value
const decodedArg = decodeAbiParameters([param], value) || []
return decodedArg[0]
}