Repository: wevm/viem (TypeScript)

Issue:
bug: decodeEventLog topics type error when cycling getLogs data
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Package Version

^0.1.7

### Current Behavior

> Type 'TopicFilter' is not assignable to type '[signature: `0x${string}`, ...args: LogTopic[]]'.
  Source provides no match for required element at position 0 in target.ts(2322)

### Expected Behavior

No type error

### Steps To Reproduce

```
const logs = await client.getLogs();

logs.forEach((log) => {
  const eventData = decodeEventLog({
    abi: [],
    data: log.data,
    topics: log.topics,
  });
});
```

### Link to Minimal Reproducible Example (StackBlitz, CodeSandbox, GitHub repo etc.)

https://stackblitz.com/edit/viem-getting-started-vqwqeb?file=index.ts

### Anything else?

_No response_
bug:  decodeEventLog silently fails to decode data when event signature matches but indexed param differs
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Package Version

0.1.9

### Current Behavior

the following script should either fails or alternatively be lenient about the log data passed in (it can for example attempt to parse the data field when the topics  length do not matches the indexed arguments)

```js
import { decodeEventLog } from "viem";

const abi = [
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "from",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        indexed: true,
        internalType: "uint256",
        name: "id",
        type: "uint256",
      },
    ],
    name: "Transfer",
    type: "event",
  },
];
const parsed = decodeEventLog({
  abi,
  data: "0x0000000000000000000000000000000000000000000000000000000023c34600",
  topics: [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266",
    "0x00000000000000000000000070e8a65d014918798ba424110d5df658cde1cc58",
  ],
});

console.log(parsed);

```

The script above output the following

```
{
  eventName: 'Transfer',
  args: {
    from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    to: '0x70E8a65d014918798BA424110d5Df658cdE1cC58'
  }
}
```

See missing third argument ?
`decodeEventLog` expected to parse 3 indexed argument but did not fails when it only parsed 2 of them

Note this event comes form an ERC20 contract who do not have the amount as indexed param
The abi passed in the function is an ERC721 which expect all argument to be indexed.



### Expected Behavior

The function should fails when not all indexed argument are found

Alternatively it could attempt to parse the non-present indexed arguments in the data field



### Steps To Reproduce

```js
import { decodeEventLog } from "viem";

const abi = [
  {
    anonymous: false,
    inputs: [
      {
        indexed: true,
        internalType: "address",
        name: "from",
        type: "address",
      },
      {
        indexed: true,
        internalType: "address",
        name: "to",
        type: "address",
      },
      {
        indexed: true,
        internalType: "uint256",
        name: "id",
        type: "uint256",
      },
    ],
    name: "Transfer",
    type: "event",
  },
];
const parsed = decodeEventLog({
  abi,
  data: "0x0000000000000000000000000000000000000000000000000000000023c34600",
  topics: [
    "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
    "0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266",
    "0x00000000000000000000000070e8a65d014918798ba424110d5df658cde1cc58",
  ],
});

console.log(parsed);

```

### Link to Minimal Reproducible Example (StackBlitz, CodeSandbox, GitHub repo etc.)

https://stackblitz.com/edit/viem-getting-started-oryrtm?file=index.ts

### Anything else?

_No response_


Discussion: Related [discussion](https://github.com/wagmi-dev/viem/discussions/163) and [PR](https://github.com/wagmi-dev/viem/pull/178).

It may not make sense for `decodeEventLog` to require the first element of the `topics` array to be a `Hex`, because anonymous events with no indexed parameters have `topics == []`.

Related question: does `decodeEventLog` work for anonymous events at the moment?
Not right now. I'm not sure you can decode `data` from an anonymous log if you can't derive a signature. 

Thinking more about it, the proper Expected Behavior is to fails, I made a PR for it : https://github.com/wagmi-dev/viem/pull/198

But it might be convenient to have an option to be lenient ?





Fix (patch):
diff --git a/.changeset/warm-jokes-dress.md b/.changeset/warm-jokes-dress.md
new file mode 100644
index 0000000000..364d1aaeca
--- /dev/null
+++ b/.changeset/warm-jokes-dress.md
@@ -0,0 +1,5 @@
+---
+"viem": patch
+---
+
+Added an assertion in `decodeEventLog` to check for a mismatch between topics + indexed event parameters.
diff --git a/src/errors/abi.ts b/src/errors/abi.ts
index 6b3225d0f5..131ebfc0cf 100644
--- a/src/errors/abi.ts
+++ b/src/errors/abi.ts
@@ -1,5 +1,6 @@
-import { Hex } from '../types'
-import { size } from '../utils'
+import { AbiParameter } from 'abitype'
+import { AbiItem, Hex } from '../types'
+import { formatAbiItem, size } from '../utils'
 import { BaseError } from './base'
 
 export class AbiConstructorNotFoundError extends BaseError {
@@ -230,6 +231,25 @@ export class BytesSizeMismatchError extends BaseError {
   }
 }
 
+export class DecodeLogTopicsMismatch extends BaseError {
+  name = 'DecodeLogTopicsMismatch'
+  constructor({
+    abiItem,
+    param,
+  }: {
+    abiItem: AbiItem
+    param: AbiParameter & { indexed: boolean }
+  }) {
+    super(
+      [
+        `Expected a topic for indexed event parameter${
+          param.name ? ` "${param.name}"` : ''
+        } on event "${formatAbiItem(abiItem, { includeName: true })}".`,
+      ].join('\n'),
+    )
+  }
+}
+
 export class InvalidAbiEncodingTypeError extends BaseError {
   name = 'InvalidAbiEncodingType'
   constructor(type: string, { docsPath }: { docsPath: string }) {
diff --git a/src/errors/index.ts b/src/errors/index.ts
index b91edeca61..35c5e8932a 100644
--- a/src/errors/index.ts
+++ b/src/errors/index.ts
@@ -15,6 +15,7 @@ export {
   AbiFunctionOutputsNotFoundError,
   AbiFunctionSignatureNotFoundError,
   BytesSizeMismatchError,
+  DecodeLogTopicsMismatch,
   InvalidAbiDecodingTypeError,
   InvalidAbiEncodingTypeError,
   InvalidArrayError,
diff --git a/src/index.ts b/src/index.ts
index 9171549116..345cf657c2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -56,6 +56,7 @@ export {
   ContractFunctionZeroDataError,
   DataLengthTooLongError,
   DataLengthTooShortError,
+  DecodeLogTopicsMismatch,
   EstimateGasExecutionError,
   ExecutionRevertedError,
   FeeCapTooHighError,
diff --git a/src/utils/abi/decodeEventLog.ts b/src/utils/abi/decodeEventLog.ts
index f606bf485a..26a2d85ada 100644
--- a/src/utils/abi/decodeEventLog.ts
+++ b/src/utils/abi/decodeEventLog.ts
@@ -1,11 +1,13 @@
 import { Abi, AbiParameter, Narrow } from 'abitype'
-import { AbiEventSignatureNotFoundError } from '../../errors'
+import {
+  AbiEventSignatureNotFoundError,
+  DecodeLogTopicsMismatch,
+} from '../../errors'
 import {
   EventDefinition,
   ExtractEventArgsFromTopics,
   ExtractEventNameFromAbi,
   Hex,
-  LogTopic,
 } from '../../types'
 import { getEventSelector } from '../hash'
 import { decodeAbiParameters } from './decodeAbiParameters'
@@ -14,7 +16,7 @@ import { formatAbiItem } from './formatAbiItem'
 export type DecodeEventLogParameters<
   TAbi extends Abi | readonly unknown[] = Abi,
   TEventName extends string = string,
-  TTopics extends LogTopic[] = LogTopic[],
+  TTopics extends Hex[] = Hex[],
   TData extends Hex | undefined = undefined,
 > = {
   abi: Narrow<TAbi>
@@ -26,7 +28,7 @@ export type DecodeEventLogParameters<
 export type DecodeEventLogReturnType<
   TAbi extends Abi | readonly unknown[] = Abi,
   TEventName extends string = string,
-  TTopics extends LogTopic[] = LogTopic[],
+  TTopics extends Hex[] = Hex[],
   TData extends Hex | undefined = undefined,
 > = {
   eventName: TEventName
@@ -35,7 +37,7 @@ export type DecodeEventLogReturnType<
 export function decodeEventLog<
   TAbi extends Abi | readonly unknown[],
   TEventName extends string,
-  TTopics extends LogTopic[],
+  TTopics extends Hex[],
   TData extends Hex | undefined = undefined,
 >({
   abi,
@@ -64,14 +66,16 @@ export function decodeEventLog<
   let args: any = isUnnamed ? [] : {}
 
   // Decode topics (indexed args).
-  for (let i = 0; i < inputs.length; i++) {
-    const param = inputs[i]
-    const topic = argTopics[i]
-    if (topic === null) args[param.name || i] = null
-    if (!topic) continue
-    if (Array.isArray(topic)) {
-      args[param.name || i] = topic.map((t) => decodeTopic({ param, value: t }))
-    } else {
+  if (argTopics.length > 0) {
+    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[param.name || i] = decodeTopic({ param, value: topic })
     }
   }