/* 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 . */ import { AbiError } from 'web3-errors'; import { AbiParameter } from 'web3-types'; import { uint8ArrayConcat } from 'web3-utils'; import { DecoderResult, EncoderResult } from '../types.js'; // eslint-disable-next-line import/no-cycle import { decodeParamFromAbiParameter, encodeParamFromAbiParameter } from './index.js'; import { encodeDynamicParams } from './utils.js'; import { isDynamic } from '../utils.js'; import { decodeNumber } from './number.js'; export function encodeTuple(param: AbiParameter, input: unknown): EncoderResult { let dynamic = false; if (!Array.isArray(input) && typeof input !== 'object') { throw new AbiError('param must be either Array or Object', { param, input, }); } const narrowedInput = input as Array | Record; const encoded: Array = []; for (let i = 0; i < (param.components?.length ?? 0); i += 1) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const paramComponent = param.components![i]; let result: EncoderResult; if (Array.isArray(narrowedInput)) { if (i >= narrowedInput.length) { throw new AbiError('input param length missmatch', { param, input, }); } result = encodeParamFromAbiParameter(paramComponent, narrowedInput[i]); } else { const paramInput = narrowedInput[paramComponent.name ?? '']; // eslint-disable-next-line no-null/no-null if (paramInput === undefined || paramInput === null) { throw new AbiError('missing input defined in abi', { param, input, paramName: paramComponent.name, }); } result = encodeParamFromAbiParameter(paramComponent, paramInput); } if (result.dynamic) { dynamic = true; } encoded.push(result); } if (dynamic) { return { dynamic: true, encoded: encodeDynamicParams(encoded), }; } return { dynamic: false, encoded: uint8ArrayConcat(...encoded.map(e => e.encoded)), }; } export function decodeTuple( param: AbiParameter, bytes: Uint8Array, ): DecoderResult<{ [key: string]: unknown; __length__: number }> { const result: { [key: string]: unknown; __length__: number } = { __length__: 0, }; // tracks how much static params consumed bytes let consumed = 0; if (!param.components) { return { result, encoded: bytes, consumed, }; } // track how much dynamic params consumed bytes let dynamicConsumed = 0; for (const [index, childParam] of param.components.entries()) { let decodedResult: DecoderResult; if (isDynamic(childParam)) { // if dynamic, we will have offset encoded const offsetResult = decodeNumber( { type: 'uint32', name: '' }, bytes.subarray(consumed), ); // offset counts from start of original byte sequence decodedResult = decodeParamFromAbiParameter( childParam, bytes.subarray(Number(offsetResult.result)), ); consumed += offsetResult.consumed; dynamicConsumed += decodedResult.consumed; } else { // static param, just decode decodedResult = decodeParamFromAbiParameter(childParam, bytes.subarray(consumed)); consumed += decodedResult.consumed; } result.__length__ += 1; result[index] = decodedResult.result; if (childParam.name && childParam.name !== '') { result[childParam.name] = decodedResult.result; } } return { encoded: bytes.subarray(consumed + dynamicConsumed), result, consumed: consumed + dynamicConsumed, }; }