/*
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,
};
}