Repository: shazow/whatsabi (ts)

Issue:
Duplicate event entries with identical hashes for unverified contracts
WhatsABI is generating duplicate event ABI entries with the same hashes when attempting to `autoload` the ABI for some contracts.

## Steps to reproduce
Run `autoload` on the address with no ABIoader and let WhatsABI guess the ABI.

**Example contract**
https://etherscan.io/address/0xd9db270c1b5e3bd161e8c8503c55ceabee709552#code

**Sample ABI (irrelevant entries omitted)**
```json
{
  "address": "0xd9db270c1b5e3bd161e8c8503c55ceabee709552",
  "abi": [
    {
      "type": "event",
      "hash": "0x9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26",
      "sig": "AddedOwner(address)",
      "anonymous": false,
      "name": "AddedOwner",
      "inputs": [
        {
          "type": "address",
          "name": ""
        }
      ]
    },
    {
      "type": "event",
      "hash": "0x9465fa0c962cc76958e6373a993326400c1c94f8be2fe3a952adfa7f60b2ea26",
      "sig": "AddedOwner(address)",
      "anonymous": false,
      "name": "AddedOwner",
      "inputs": [
        {
          "type": "address",
          "name": ""
        }
      ]
    }
  ],
  "proxies": []
}
```

## Expected behavior
Each event should be unique within the ABI with no duplicate entries for the same event hash.



Pull request description:
src/disasm.ts: Use Set for eventCandidates to avoid dupes
Fixes #146

Patch:
diff --git a/src/disasm.ts b/src/disasm.ts
index 35f08ad..d49411b 100644
--- a/src/disasm.ts
+++ b/src/disasm.ts
@@ -150,7 +150,7 @@ export class Program {
     notPayable: { [key: number]: number }; // instruction offset -> bytes offset
     fallback?: number; // instruction offset for fallback function
 
-    eventCandidates: Array<string>; // PUSH32 found before a LOG instruction
+    eventCandidates: Set<string>; // PUSH32 found before a LOG instruction
     proxySlots: Array<string>; // PUSH32 found that match known proxy slots
     proxies: Array<ProxyResolver>;
     isFactory: boolean; // CREATE or CREATE2 detected
@@ -161,7 +161,7 @@ export class Program {
         this.dests = {};
         this.selectors = {};
         this.notPayable = {};
-        this.eventCandidates = [];
+        this.eventCandidates = new Set();
         this.proxySlots = [];
         this.proxies = [];
         this.isFactory = false;
@@ -267,7 +267,7 @@ export function disasm(bytecode: string, config?: {onlyJumpTable: boolean}): Pro
             }
             continue
         } else if (isLog(inst) && lastPush32.length > 0) {
-            p.eventCandidates.push(bytesToHex(lastPush32));
+            p.eventCandidates.add(bytesToHex(lastPush32));
             continue
         }
 


Test patch:
diff --git a/src/__tests__/edges.test.ts b/src/__tests__/edges.test.ts
index 4cf0f53..818e9d1 100644
--- a/src/__tests__/edges.test.ts
+++ b/src/__tests__/edges.test.ts
@@ -2,6 +2,7 @@ import { expect, test } from 'vitest';
 
 import { cached_test, describe_cached } from "./env";
 import { selectorsFromBytecode } from '../index';
+import { disasm } from '../disasm';
 
 //const address = "0xbadc0defafcf6d4239bdf0b66da4d7bd36fcf05a";
 //const missingSelector = "0x69277b67";
@@ -83,6 +84,20 @@ cached_test('issue 70: negated selector', async ({ provider, withCache }) => {
   expect(r).toEqual(expect.arrayContaining(["0xfd9f1e10"]))
 });
 
+cached_test('issue 146: duplicated events', async ({ provider, withCache }) => {
+  const address = "0xd9db270c1b5e3bd161e8c8503c55ceabee709552";
+
+  const code = await withCache(
+    `${address}_code`,
+    async () => {
+      return await provider.getCode(address)
+    },
+  )
+
+  const r = disasm(code);
+  expect(Array.from(r.eventCandidates).length).toEqual(14);
+});
+
 // Addresses with false positive zero selectors:
 // 0x99aa182ed0e2b6c47132e95686d2c73cdeff307f
 // 0xb1116d0a09f06d3e22a264c0c233d80e93abec10