TypeChain icon indicating copy to clipboard operation
TypeChain copied to clipboard

Why doesn't decodeEventLog return a typed result?

Open samlaf opened this issue 3 years ago • 8 comments
trafficstars

For a bit of context, queryFilter() results are typed, I believe by this code: https://github.com/dethcrypto/TypeChain/blob/47ab6513aaf960ce2a0425fdf6e0561ca482b182/packages/target-ethers-v5/src/codegen/events.ts#L92 For example, in my use case of interacting with the AaveV3 Pool contract, I can

const events: LiquidationCallEvent[] = await AaveV3Pool.queryFilter(AaveV3Pool.filters.LiquidationCall());

and then I can type-safely have access to the liquidation call event args. For example, events[0].args.liquidator works.

But for some reason (maybe there is a good reason?) they aren't used for decodeEventLog results. So for example, if instead I get the logs manually and parse them for the LiquidationCall event:

const liquidationCallFilterWithBlockRanges = {
    ...AaveV3Pool.filters.LiquidationCall(),
    fromBlock: 0,
    toBlock: "latest"
};
const liquidationLogs = await ethers.provider.getLogs(liquidationCallFilterWithBlockRanges);
const parsedLiquidationLogs = liquidationLogs.map(log => {
    return AaveV3Pool.interface.decodeEventLog(AaveV3Pool.interface.events["LiquidationCall(address,address,address,uint256,uint256,address,bool)"], log.data, log.topics);
})

then parsedLiquidationLogs has type Result[]. Is there a reason for this? Why can't AaveV3Pool.interface's parsing methods return typed objects like queryFilter?

samlaf avatar Jul 01 '22 10:07 samlaf

There are lot of methods in the ethers interface object like decodeEventLog, decodeFunctionData, decodeErrorData. Overloads for them currently are not generated hence you see the default type from ethers.js core. You can import the event and typecast for now.

I understand this is a feature request, I do not use this method quite frequently but if there's demand I can find some time to add this over weekends. Otherwise yeah, anyone is welcome to contribute, this is OSS :)

zemse avatar Jul 01 '22 13:07 zemse

My main use case is when I send a transaction and need to parse some state changes that are only output as events, or similarly when I'm analyzing other transactions and checking if they emitted a certain event. Once I've found the event I want to be able to parse it to have access to its fields in a nice interface (don't need to remember the ordering of all the arguments).

You say you don't use these methods a lot. Do you have a workaround for what I'm trying to do? If not, I would really like to see this implemented... :D Voting +1

samlaf avatar Jul 01 '22 13:07 samlaf

I'd like to see this implemented as well. As a workaround, I'm using this helper function to use the generated typed filters against events sourced from the transaction receipt:

/**
 * Finds the events that match the specified filter, and
 * returns these parsed and mapped to the appropriate type
 */
export function matchEvents<TArgsArray extends any[], TArgsObject>(
  events: ethers.Event[],
  contract: ethers.BaseContract,
  eventFilter: TypedEventFilter<TypedEvent<TArgsArray, TArgsObject>>
): TypedEvent<TArgsArray, TArgsObject>[] {
  return events
    .filter((ev) => matchTopics(eventFilter.topics, ev.topics))
    .map((ev) => {
      const args = contract.interface.parseLog(ev).args;
      const result: TypedEvent<TArgsArray, TArgsObject> = {
        ...ev,
        args: args as TArgsArray & TArgsObject,
      };
      return result;
    });
}

function matchTopics(
  filter: Array<string | Array<string>> | undefined,
  value: Array<string>
): boolean {
  // Implement the logic for topic filtering as described here:
  // https://docs.ethers.io/v5/concepts/events/#events--filters
  if (!filter) {
    return false;
  }
  for (let i = 0; i < filter.length; i++) {
    const f = filter[i];
    const v = value[i];
    if (typeof f == 'string') {
      if (f !== v) {
        return false;
      }
    } else {
      if (f.indexOf(v) === -1) {
        return false;
      }
    }
  }
  return true;
}

used like this:

    const transferEvent = first(
      matchEvents(
        receipt?.events || [],
        pairContract,
        pairContract.filters.Transfer()
      )
    );

mountainpath9 avatar Jul 06 '22 00:07 mountainpath9

I ran into this myself today and would love for decodeEventLog to be fully typed just like decodeFunctionResult

That would help so much in working with indexing blockchain events.

Raynos avatar May 10 '23 01:05 Raynos

I completely agree with the convo above. Having the decodeEventLog return a typed response would be only natural and actually help make my code a lot more readable. Would be great if someone had the time to implement that!

AdrianKoch3010 avatar Dec 23 '23 19:12 AdrianKoch3010