/* 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'; // eslint-disable-next-line import/no-cycle import { decodeParamFromAbiParameter, encodeNumber, encodeParamFromAbiParameter } from './index.js'; import { DecoderResult, EncoderResult } from '../types.js'; import { extractArrayType, isDynamic, WORD_SIZE } from '../utils.js'; import { decodeNumber } from './number.js'; import { encodeDynamicParams } from './utils.js'; export function encodeArray(param: AbiParameter, values: unknown): EncoderResult { if (!Array.isArray(values)) { throw new AbiError('Expected value to be array', { abi: param, values }); } const { size, param: arrayItemParam } = extractArrayType(param); const encodedParams = values.map(v => encodeParamFromAbiParameter(arrayItemParam, v)); const dynamic = size === -1; const dynamicItems = encodedParams.length > 0 && encodedParams[0].dynamic; if (!dynamic && values.length !== size) { throw new AbiError("Given arguments count doesn't match array length", { arrayLength: size, argumentsLength: values.length, }); } if (dynamic || dynamicItems) { const encodingResult = encodeDynamicParams(encodedParams); if (dynamic) { const encodedLength = encodeNumber( { type: 'uint256', name: '' }, encodedParams.length, ).encoded; return { dynamic: true, encoded: encodedParams.length > 0 ? uint8ArrayConcat(encodedLength, encodingResult) : encodedLength, }; } return { dynamic: true, encoded: encodingResult, }; } return { dynamic: false, encoded: uint8ArrayConcat(...encodedParams.map(p => p.encoded)), }; } export function decodeArray(param: AbiParameter, bytes: Uint8Array): DecoderResult { // eslint-disable-next-line prefer-const let { size, param: arrayItemParam } = extractArrayType(param); const dynamic = size === -1; let consumed = 0; const result: unknown[] = []; let remaining = bytes; // dynamic array, we need to decode length if (dynamic) { const lengthResult = decodeNumber({ type: 'uint32', name: '' }, bytes); size = Number(lengthResult.result); consumed = lengthResult.consumed; remaining = lengthResult.encoded; } const hasDynamicChild = isDynamic(arrayItemParam); if (hasDynamicChild) { // known length but dynamic child, each child is actually head element with encoded offset for (let i = 0; i < size; i += 1) { const offsetResult = decodeNumber( { type: 'uint32', name: '' }, remaining.subarray(i * WORD_SIZE), ); consumed += offsetResult.consumed; const decodedChildResult = decodeParamFromAbiParameter( arrayItemParam, remaining.subarray(Number(offsetResult.result)), ); consumed += decodedChildResult.consumed; result.push(decodedChildResult.result); } return { result, encoded: remaining.subarray(consumed), consumed, }; } for (let i = 0; i < size; i += 1) { // decode static params const decodedChildResult = decodeParamFromAbiParameter( arrayItemParam, bytes.subarray(consumed), ); consumed += decodedChildResult.consumed; result.push(decodedChildResult.result); } return { result, encoded: bytes.subarray(consumed), consumed, }; }