/*
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, Bytes } from 'web3-types';
import { bytesToHex, bytesToUint8Array } from 'web3-utils';
import { isBytes, ValidInputTypes } from 'web3-validator';
import { DecoderResult, EncoderResult } from '../types.js';
import { alloc, WORD_SIZE } from '../utils.js';
import { decodeNumber, encodeNumber } from './number.js';
const MAX_STATIC_BYTES_COUNT = 32;
export function encodeBytes(param: AbiParameter, input: unknown): EncoderResult {
// hack for odd length hex strings
if (typeof input === 'string' && input.length % 2 !== 0) {
// eslint-disable-next-line no-param-reassign
input += '0';
}
if (!isBytes(input as ValidInputTypes)) {
throw new AbiError('provided input is not valid bytes value', {
type: param.type,
value: input,
name: param.name,
});
}
const bytes = bytesToUint8Array(input as Bytes);
const [, size] = param.type.split('bytes');
// fixed size
if (size) {
if (Number(size) > MAX_STATIC_BYTES_COUNT || Number(size) < 1) {
throw new AbiError(
'invalid bytes type. Static byte type can have between 1 and 32 bytes',
{
type: param.type,
},
);
}
if (Number(size) < bytes.length) {
throw new AbiError('provided input size is different than type size', {
type: param.type,
value: input,
name: param.name,
});
}
const encoded = alloc(WORD_SIZE);
encoded.set(bytes);
return {
dynamic: false,
encoded,
};
}
const partsLength = Math.ceil(bytes.length / WORD_SIZE);
// one word for length of data + WORD for each part of actual data
const encoded = alloc(WORD_SIZE + partsLength * WORD_SIZE);
encoded.set(encodeNumber({ type: 'uint32', name: '' }, bytes.length).encoded);
encoded.set(bytes, WORD_SIZE);
return {
dynamic: true,
encoded,
};
}
export function decodeBytes(param: AbiParameter, bytes: Uint8Array): DecoderResult {
const [, sizeString] = param.type.split('bytes');
let size = Number(sizeString);
let remainingBytes = bytes;
let partsCount = 1;
let consumed = 0;
if (!size) {
// dynamic bytes
const result = decodeNumber({ type: 'uint32', name: '' }, remainingBytes);
size = Number(result.result);
consumed += result.consumed;
remainingBytes = result.encoded;
partsCount = Math.ceil(size / WORD_SIZE);
}
if (size > bytes.length) {
throw new AbiError('there is not enough data to decode', {
type: param.type,
encoded: bytes,
size,
});
}
return {
result: bytesToHex(remainingBytes.subarray(0, size)),
encoded: remainingBytes.subarray(partsCount * WORD_SIZE),
consumed: consumed + partsCount * WORD_SIZE,
};
}