/*
This file is part of web3.js.
web3.js is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
web3.js is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with web3.js. If not, see .
*/
/**
*
* @module ABI
*/
import { HexString, AbiParameter, DecodedParams } from 'web3-types';
import { decodeParameter, decodeParametersWith } from './parameters_api.js';
const STATIC_TYPES = ['bool', 'string', 'int', 'uint', 'address', 'fixed', 'ufixed'];
const _decodeParameter = (inputType: string, clonedTopic: string) =>
inputType === 'string' ? clonedTopic : decodeParameter(inputType, clonedTopic);
/**
* Decodes ABI-encoded log data and indexed topic data.
* @param inputs - A {@link AbiParameter} input array. See the [Solidity documentation](https://docs.soliditylang.org/en/develop/types.html) for a list of types.
* @param data - The ABI byte code in the `data` field of a log.
* @param topics - An array with the index parameter topics of the log, without the topic[0] if its a non-anonymous event, otherwise with topic[0]
* @returns - The result object containing the decoded parameters.
*
* @example
* ```ts
* let res = web3.eth.abi.decodeLog(
* [
* {
* type: "string",
* name: "myString",
* },
* {
* type: "uint256",
* name: "myNumber",
* indexed: true,
* },
* {
* type: "uint8",
* name: "mySmallNumber",
* indexed: true,
* },
* ],
* "0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000748656c6c6f252100000000000000000000000000000000000000000000000000",
* [
* "0x000000000000000000000000000000000000000000000000000000000000f310",
* "0x0000000000000000000000000000000000000000000000000000000000000010",
* ]
* );
* > {
* '0': 'Hello%!',
* '1': 62224n,
* '2': 16n,
* __length__: 3,
* myString: 'Hello%!',
* myNumber: 62224n,
* mySmallNumber: 16n
* }
* ```
*/
export const decodeLog = (
inputs: Array | ReadonlyArray,
data: HexString,
topics: string | string[],
) => {
const clonedTopics = Array.isArray(topics) ? topics : [topics];
const indexedInputs: Record = {};
const nonIndexedInputs: Record = {};
for (const [i, input] of inputs.entries()) {
if (input.indexed) {
indexedInputs[i] = input;
} else {
nonIndexedInputs[i] = input;
}
}
const decodedNonIndexedInputs: DecodedParams = data
? decodeParametersWith(Object.values(nonIndexedInputs), data, true)
: { __length__: 0 };
// If topics are more than indexed inputs, that means first topic is the event signature
const offset = clonedTopics.length - Object.keys(indexedInputs).length;
const decodedIndexedInputs = Object.values(indexedInputs).map((input, index) =>
STATIC_TYPES.some(s => input.type.startsWith(s))
? _decodeParameter(input.type, clonedTopics[index + offset])
: clonedTopics[index + offset],
);
const returnValues: DecodedParams = { __length__: 0 };
let indexedCounter = 0;
let nonIndexedCounter = 0;
for (const [i, res] of inputs.entries()) {
returnValues[i] = res.type === 'string' ? '' : undefined;
if (indexedInputs[i]) {
returnValues[i] = decodedIndexedInputs[indexedCounter];
indexedCounter += 1;
}
if (nonIndexedInputs[i]) {
returnValues[i] = decodedNonIndexedInputs[String(nonIndexedCounter)];
nonIndexedCounter += 1;
}
if (res.name) {
returnValues[res.name] = returnValues[i];
}
returnValues.__length__ += 1;
}
return returnValues as ReturnType;
};