Etherscan – Querying Events Using Ethers.js Returns Logs with Unrelated Contract Addresses

abietherscaneventslogsquery

The UniswapV2Factory contract has the following event:

event PairCreated(address indexed token0, address indexed token1, address pair, uint)

I am using etherjs to detect all events on chain in the last 5 blocks where this event has been emitted (I've included the code snippet I've used below). An example of a log that was detected is shown below, I want to know why log has been detected when the contract address I've provided in my below code snippet does NOT use this contract address.

{
    "_type": "log",
    "address": "0xd5B823e1De4476E1172f84d42f70A69e15AAb441", // WTF? Why is this appearing?
    "blockHash": "0xa25f041174983789e650ad2d7fe329535c950c179327d435c64fa7603226c90b",
    "blockNumber": 18315859,
    "data": "0x0000000000000000000000000000000000000000000007c7ce7bbaa0623c1757",
    "index": 284,
    "removed": false,
    "topics": [
      "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
      "0x000000000000000000000000b0056438ecdacf8b4e6722d30d6a0d4cbc3a5f51",
      "0x000000000000000000000000663a5c229c09b049e36dcc11a9b0d4a8eb9db214"
    ], 
    "transactionHash": "0x6566d5fac82e67222f8b95338f957aefa83f2e1ede030b34937a9c92d9771d22",
    "transactionIndex": 117
}

Prior to filtering my log object there are around 19506 log objects, when I filter my log objects with the UniswapV2Factory contract address (0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f) there are 6 objects which seems more realistic. I can imagine Uniswap deploying 6 Liquidity Pools (which consist of Token Pairs, hence emitting the PairCreated event 6 times)…

My question is why did 19506 logs return? When my Contract objects constructor parameter explicitly receives the contractAddress I am interested in.

const main = async () => {
  try {
    const provider: JsonRpcProvider = new ethers.JsonRpcProvider(
      JSON_RPC_HTTPS_URL
    );

    const currentBlockNumber: number = 18315860; //await provider.getBlockNumber();
    console.log(`Current Block Number: ${currentBlockNumber}`);
    const contract: Contract = new ethers.Contract(
      contractAddress,
      abiJson,
      provider
    );

    console.log(`Contract: ${JSON.stringify(contract)}`);
    let filter: DeferredTopicFilter = contract.filters.PairCreated([]);
    console.log(`Filter: ${JSON.stringify(filter)}`);

    let logs: Log[] = await provider.getLogs({
      ...filter,
      fromBlock: currentBlockNumber - 50,
      toBlock: currentBlockNumber - 1,
    });

    console.log(
      `Logs (${logs.length}): ${JSON.stringify(
        logs.filter(
          (log: Log, idx: number) =>
            log.address === "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
        )
      )}`
    );
    console.log(`Logs (${logs.length})`);
    console.log(`Current Block: ${currentBlockNumber}`);
  } catch (error: any) {
    console.log(`Error occurred: ${error}`);
  }
};

Best Answer

The issue seems to stem from the differences between how getLogs() behaves in ethers v5 and ethers v6. This change in behavior isn't well-documented, leading to confusion.

For more precise and specific querying for contract events, ethers provides the queryFilter() method on a contract instance. This method is more intuitive for querying events and is the recommended approach.

Here's a code snippet that demonstrates its usage:

const provider = new ethers.JsonRpcProvider(
    "https://eth.llamarpc.com"
);

const currentBlockNumber: number = 18315860;
console.log(`Current Block Number: ${currentBlockNumber}`);

const contract: ethers.Contract = new ethers.Contract(
    "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f",
    abi,
    provider
);

let filter = (contract.filters as any).PairCreated();

let logs = await contract.queryFilter(
    filter,
    currentBlockNumber - 50,
    currentBlockNumber - 1,
);

console.log(`Logs related to PairCreated (${logs.length}): ${JSON.stringify(logs)}`);

The queryFilter() method essentially wraps around the getLogs() method. It does the necessary work to structure the filter correctly, ensuring that logs specific to the contract and event in question are fetched. You can find the underlying implementation of queryFilter() in ethers v6 here: https://github.com/ethers-io/ethers.js/blob/main/src.ts/contract/contract.ts#L933