/*
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 { format, isNullish, keccak256 } from 'web3-utils';
import {
AbiConstructorFragment,
AbiEventFragment,
AbiFunctionFragment,
Filter,
HexString,
Topic,
FMT_NUMBER,
FMT_BYTES,
ContractOptions,
} from 'web3-types';
import {
decodeParameters,
encodeEventSignature,
encodeFunctionSignature,
encodeParameter,
encodeParameters,
inferTypesAndEncodeParameters,
isAbiConstructorFragment,
jsonInterfaceMethodToString,
} from 'web3-eth-abi';
import { blockSchema, ALL_EVENTS } from 'web3-eth';
import { Web3ContractError } from 'web3-errors';
export { decodeEventABI } from 'web3-eth';
type Writeable = { -readonly [P in keyof T]: T[P] };
export const encodeEventABI = (
{ address }: ContractOptions,
event: AbiEventFragment & { signature: string },
options?: Filter,
) => {
const topics = options?.topics;
const filter = options?.filter ?? {};
const opts: Writeable = {};
if (!isNullish(options?.fromBlock)) {
opts.fromBlock = format(blockSchema.properties.number, options?.fromBlock, {
number: FMT_NUMBER.HEX,
bytes: FMT_BYTES.HEX,
});
}
if (!isNullish(options?.toBlock)) {
opts.toBlock = format(blockSchema.properties.number, options?.toBlock, {
number: FMT_NUMBER.HEX,
bytes: FMT_BYTES.HEX,
});
}
if (topics && Array.isArray(topics)) {
opts.topics = [...topics] as Topic[];
} else {
opts.topics = [];
// add event signature
if (event && !event.anonymous && ![ALL_EVENTS, 'allEvents'].includes(event.name)) {
opts.topics.push(
event.signature ?? encodeEventSignature(jsonInterfaceMethodToString(event)),
);
}
// add event topics (indexed arguments)
if (![ALL_EVENTS, 'allEvents'].includes(event.name) && event.inputs) {
for (const input of event.inputs) {
if (!input.indexed) {
continue;
}
const value = filter[input.name];
if (!value) {
// eslint-disable-next-line no-null/no-null
opts.topics.push(null);
continue;
}
// TODO: https://github.com/ethereum/web3.js/issues/344
// TODO: deal properly with components
if (Array.isArray(value)) {
opts.topics.push(value.map(v => encodeParameter(input.type, v)));
} else if (input.type === 'string') {
opts.topics.push(keccak256(value as string));
} else {
opts.topics.push(encodeParameter(input.type, value));
}
}
}
}
if (!opts.topics.length) delete opts.topics;
if (address) {
opts.address = address.toLowerCase();
}
return opts;
};
export const encodeMethodABI = (
abi: AbiFunctionFragment | AbiConstructorFragment,
args: unknown[],
deployData?: HexString,
) => {
const inputLength = Array.isArray(abi.inputs) ? abi.inputs.length : 0;
if (abi.inputs && inputLength !== args.length) {
throw new Web3ContractError(
`The number of arguments is not matching the methods required number. You need to pass ${inputLength} arguments.`,
);
}
let params: string;
if (abi.inputs) {
params = encodeParameters(Array.isArray(abi.inputs) ? abi.inputs : [], args).replace(
'0x',
'',
);
} else {
params = inferTypesAndEncodeParameters(args).replace('0x', '');
}
if (isAbiConstructorFragment(abi)) {
if (!deployData)
throw new Web3ContractError(
'The contract has no contract data option set. This is necessary to append the constructor parameters.',
);
if (!deployData.startsWith('0x')) {
return `0x${deployData}${params}`;
}
return `${deployData}${params}`;
}
return `${encodeFunctionSignature(abi)}${params}`;
};
export const decodeMethodReturn = (abi: AbiFunctionFragment, returnValues?: HexString) => {
// If it was constructor then we need to return contract address
if (abi.type === 'constructor') {
return returnValues;
}
if (!returnValues) {
// Using "null" value intentionally to match legacy behavior
// eslint-disable-next-line no-null/no-null
return null;
}
const value = returnValues.length >= 2 ? returnValues.slice(2) : returnValues;
if (!abi.outputs) {
// eslint-disable-next-line no-null/no-null
return null;
}
const result = decodeParameters([...abi.outputs], value);
if (result.__length__ === 1) {
return result[0];
}
return result;
};