ox
Version:
Ethereum Standard Library
571 lines • 16.8 kB
JavaScript
import * as Address from './Address.js';
import * as Bytes from './Bytes.js';
import * as Errors from './Errors.js';
import * as Hex from './Hex.js';
import * as formatAbiParameters from './internal/human-readable/formatAbiParameters.js';
import * as parseAbiParameters from './internal/human-readable/parseAbiParameters.js';
import * as internal from './internal/abiParameters.js';
import * as Cursor from './internal/cursor.js';
import * as Solidity from './Solidity.js';
export { InvalidAbiParametersError, InvalidAbiTypeParameterError, InvalidFunctionModifierError, InvalidModifierError, InvalidParameterError, SolidityProtectedKeywordError, } from './internal/human-readable/errors.js';
export { InvalidParenthesisError } from './internal/human-readable/errors.js';
// eslint-disable-next-line jsdoc-js/require-jsdoc
export function decode(parameters, data, options = {}) {
const { as = 'Array', checksumAddress = true } = options;
const bytes = typeof data === 'string' ? Bytes.fromHex(data) : data;
const cursor = Cursor.create(bytes);
if (Bytes.size(bytes) === 0 && parameters.length > 0)
throw new ZeroDataError();
if (Bytes.size(bytes) && Bytes.size(bytes) < 32)
throw new DataSizeTooSmallError({
data: typeof data === 'string' ? data : Hex.fromBytes(data),
parameters: parameters,
size: Bytes.size(bytes),
});
let consumed = 0;
const values = as === 'Array' ? [] : {};
for (let i = 0; i < parameters.length; ++i) {
const param = parameters[i];
try {
// Zero-width types (e.g. `uint256[0]`, empty tuples) at the end of the
// data consume no bytes, so the cursor may already be exhausted.
if (consumed < bytes.length)
cursor.setPosition(consumed);
const [data_, consumed_] = internal.decodeParameter(cursor, param, {
checksumAddress,
staticPosition: 0,
});
consumed += consumed_;
if (as === 'Array')
values.push(data_);
else
values[param.name ?? i] = data_;
}
catch (err) {
if (err instanceof Cursor.PositionOutOfBoundsError)
throw new DataSizeTooSmallError({
data: typeof data === 'string' ? data : Hex.fromBytes(data),
parameters: parameters,
size: Bytes.size(bytes),
});
throw err;
}
}
return values;
}
/**
* Encodes primitive values into ABI encoded data as per the [Application Binary Interface (ABI) Specification](https://docs.soliditylang.org/en/latest/abi-spec).
*
* @example
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const data = AbiParameters.encode(
* AbiParameters.from(['string', 'uint', 'bool']),
* ['wagmi', 420n, true]
* )
* ```
*
* @example
* ### JSON Parameters
*
* Specify **JSON ABI** Parameters as schema:
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const data = AbiParameters.encode(
* [
* { type: 'string', name: 'name' },
* { type: 'uint', name: 'age' },
* { type: 'bool', name: 'isOwner' }
* ],
* ['wagmi', 420n, true]
* )
* ```
*
* @param parameters - The set of ABI parameters to encode, in the shape of the `inputs` or `outputs` attribute of an ABI Item. These parameters must include valid [ABI types](https://docs.soliditylang.org/en/latest/types.html).
* @param values - The set of primitive values that correspond to the ABI types defined in `parameters`.
* @returns ABI encoded data.
*/
export function encode(parameters, values, options) {
const { checksumAddress = false } = options ?? {};
if (parameters.length !== values.length)
throw new LengthMismatchError({
expectedLength: parameters.length,
givenLength: values.length,
});
// Prepare the parameters to determine dynamic types to encode.
const preparedParameters = internal.prepareParameters({
checksumAddress,
parameters: parameters,
values: values,
});
const data = internal.encode(preparedParameters);
if (data.length === 0)
return '0x';
return data;
}
/**
* Encodes an array of primitive values to a [packed ABI encoding](https://docs.soliditylang.org/en/latest/abi-spec.html#non-standard-packed-mode).
*
* @example
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const encoded = AbiParameters.encodePacked(
* ['address', 'string'],
* [
* '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
* 'hello world'
* ]
* )
* // @log: '0xd8da6bf26964af9d7eed9e03e53415d37aa9604568656c6c6f20776f726c64'
* ```
*
* @param types - Set of ABI types to pack encode.
* @param values - The set of primitive values that correspond to the ABI types defined in `types`.
* @returns The encoded packed data.
*/
export function encodePacked(types, values) {
if (types.length !== values.length)
throw new LengthMismatchError({
expectedLength: types.length,
givenLength: values.length,
});
const data = [];
for (let i = 0; i < types.length; i++) {
const type = types[i];
const value = values[i];
data.push(encodePacked.encode(type, value));
}
return Hex.concat(...data);
}
(function (encodePacked) {
// eslint-disable-next-line jsdoc-js/require-jsdoc
function encode(type, value, isArray = false) {
if (type === 'address') {
const address = value;
Address.assert(address);
return Hex.padLeft(address.toLowerCase(), isArray ? 32 : 0);
}
if (type === 'string')
return Hex.fromString(value);
if (type === 'bytes')
return value;
if (type === 'bool')
return Hex.padLeft(Hex.fromBoolean(value), isArray ? 32 : 1);
const intMatch = type.match(Solidity.integerRegex);
if (intMatch) {
const [_type, baseType, bits = '256'] = intMatch;
const size = Number.parseInt(bits, 10) / 8;
return Hex.fromNumber(value, {
size: isArray ? 32 : size,
signed: baseType === 'int',
});
}
const bytesMatch = type.match(Solidity.bytesRegex);
if (bytesMatch) {
const [_type, size] = bytesMatch;
if (Number.parseInt(size, 10) !== (value.length - 2) / 2)
throw new BytesSizeMismatchError({
expectedSize: Number.parseInt(size, 10),
value: value,
});
return Hex.padRight(value, isArray ? 32 : 0);
}
const arrayMatch = type.match(Solidity.arrayRegex);
if (arrayMatch && Array.isArray(value)) {
const [_type, childType, lengthMatch] = arrayMatch;
if (lengthMatch) {
const declaredLength = Number.parseInt(lengthMatch, 10);
if (value.length !== declaredLength)
throw new ArrayLengthMismatchError({
expectedLength: declaredLength,
givenLength: value.length,
type: type,
});
}
const data = [];
for (let i = 0; i < value.length; i++) {
data.push(encode(childType, value[i], true));
}
if (data.length === 0)
return '0x';
return Hex.concat(...data);
}
throw new InvalidTypeError(type);
}
encodePacked.encode = encode;
})(encodePacked || (encodePacked = {}));
/**
* Formats {@link ox#AbiParameters.AbiParameters} into **Human Readable ABI Parameters**.
*
* @example
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const formatted = AbiParameters.format([
* {
* name: 'spender',
* type: 'address'
* },
* {
* name: 'amount',
* type: 'uint256'
* }
* ])
*
* formatted
* // ^?
* ```
*
* @param parameters - The ABI Parameters to format.
* @returns The formatted ABI Parameters .
*/
export function format(parameters) {
return formatAbiParameters.formatAbiParameters(parameters);
}
/**
* Parses arbitrary **JSON ABI Parameters** or **Human Readable ABI Parameters** into typed {@link ox#AbiParameters.AbiParameters}.
*
* @example
* ### JSON Parameters
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const parameters = AbiParameters.from([
* {
* name: 'spender',
* type: 'address'
* },
* {
* name: 'amount',
* type: 'uint256'
* }
* ])
*
* parameters
* //^?
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* ```
*
* @example
* ### Human Readable Parameters
*
* Human Readable ABI Parameters can be parsed into a typed {@link ox#AbiParameters.AbiParameters}:
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const parameters = AbiParameters.from(
* 'address spender, uint256 amount'
* )
*
* parameters
* //^?
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* ```
*
* @example
* It is possible to specify `struct`s along with your definitions:
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* const parameters = AbiParameters.from([
* 'struct Foo { address spender; uint256 amount; }', // [!code hl]
* 'Foo foo, address bar'
* ])
*
* parameters
* //^?
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* //
* ```
*
*
*
* @param parameters - The ABI Parameters to parse.
* @returns The typed ABI Parameters.
*/
export function from(parameters) {
if (Array.isArray(parameters) && typeof parameters[0] === 'string')
return parseAbiParameters.parseAbiParameters(parameters);
if (typeof parameters === 'string')
return parseAbiParameters.parseAbiParameters(parameters);
return parameters;
}
/**
* Throws when the data size is too small for the given parameters.
*
* @example
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* AbiParameters.decode([{ type: 'uint256' }], '0x010f')
* // ↑ ❌ 2 bytes
* // @error: AbiParameters.DataSizeTooSmallError: Data size of 2 bytes is too small for given parameters.
* // @error: Params: (uint256)
* // @error: Data: 0x010f (2 bytes)
* ```
*
* ### Solution
*
* Pass a valid data size.
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* AbiParameters.decode(
* [{ type: 'uint256' }],
* '0x00000000000000000000000000000000000000000000000000000000000010f'
* )
* // ↑ ✅ 32 bytes
* ```
*/
export class DataSizeTooSmallError extends Errors.BaseError {
name = 'AbiParameters.DataSizeTooSmallError';
constructor({ data, parameters, size, }) {
super(`Data size of ${size} bytes is too small for given parameters.`, {
metaMessages: [
`Params: (${formatAbiParameters.formatAbiParameters(parameters)})`,
`Data: ${data} (${size} bytes)`,
],
});
}
}
/**
* Throws when zero data is provided, but data is expected.
*
* @example
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* AbiParameters.decode([{ type: 'uint256' }], '0x')
* // ↑ ❌ zero data
* // @error: AbiParameters.DataSizeTooSmallError: Data size of 2 bytes is too small for given parameters.
* // @error: Params: (uint256)
* // @error: Data: 0x010f (2 bytes)
* ```
*
* ### Solution
*
* Pass valid data.
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* AbiParameters.decode(
* [{ type: 'uint256' }],
* '0x00000000000000000000000000000000000000000000000000000000000010f'
* )
* // ↑ ✅ 32 bytes
* ```
*/
export class ZeroDataError extends Errors.BaseError {
name = 'AbiParameters.ZeroDataError';
constructor() {
super('Cannot decode zero data ("0x") with ABI parameters.');
}
}
/**
* The length of the array value does not match the length specified in the corresponding ABI parameter.
*
* ### Example
*
* ```ts twoslash
* // @noErrors
* import { AbiParameters } from 'ox'
* // ---cut---
* AbiParameters.encode(AbiParameters.from('uint256[3]'), [
* [69n, 420n]
* ])
* // ↑ expected: 3 ↑ ❌ length: 2
* // @error: AbiParameters.ArrayLengthMismatchError: ABI encoding array length mismatch
* // @error: for type `uint256[3]`. Expected: `3`. Given: `2`.
* ```
*
* ### Solution
*
* Pass an array of the correct length.
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
* // ---cut---
* AbiParameters.encode(AbiParameters.from(['uint256[3]']), [
* [69n, 420n, 69n]
* ])
* // ↑ ✅ length: 3
* ```
*/
export class ArrayLengthMismatchError extends Errors.BaseError {
name = 'AbiParameters.ArrayLengthMismatchError';
constructor({ expectedLength, givenLength, type, }) {
super(`Array length mismatch for type \`${type}\`. Expected: \`${expectedLength}\`. Given: \`${givenLength}\`.`);
}
}
/**
* The size of the bytes value does not match the size specified in the corresponding ABI parameter.
*
* ### Example
*
* ```ts twoslash
* // @noErrors
* import { AbiParameters } from 'ox'
* // ---cut---
* AbiParameters.encode(AbiParameters.from('bytes8'), [
* ['0xdeadbeefdeadbeefdeadbeef']
* ])
* // ↑ expected: 8 bytes ↑ ❌ size: 12 bytes
* // @error: BytesSizeMismatchError: Size of bytes "0xdeadbeefdeadbeefdeadbeef"
* // @error: (bytes12) does not match expected size (bytes8).
* ```
*
* ### Solution
*
* Pass a bytes value of the correct size.
*
* ```ts twoslash
* import { AbiParameters } from 'ox'
* // ---cut---
* AbiParameters.encode(AbiParameters.from(['bytes8']), [
* '0xdeadbeefdeadbeef'
* ])
* // ↑ ✅ size: 8 bytes
* ```
*/
export class BytesSizeMismatchError extends Errors.BaseError {
name = 'AbiParameters.BytesSizeMismatchError';
constructor({ expectedSize, value, }) {
super(`Size of bytes "${value}" (bytes${Hex.size(value)}) does not match expected size (bytes${expectedSize}).`);
}
}
/**
* The length of the values to encode does not match the length of the ABI parameters.
*
* ### Example
*
* ```ts twoslash
* // @noErrors
* import { AbiParameters } from 'ox'
* // ---cut---
* AbiParameters.encode(
* AbiParameters.from(['string', 'uint256']),
* ['hello']
* )
* // @error: LengthMismatchError: ABI encoding params/values length mismatch.
* // @error: Expected length (params): 2
* // @error: Given length (values): 1
* ```
*
* ### Solution
*
* Pass the correct number of values to encode.
*
* ### Solution
*
* Pass a [valid ABI type](https://docs.soliditylang.org/en/develop/abi-spec.html#types).
*/
export class LengthMismatchError extends Errors.BaseError {
name = 'AbiParameters.LengthMismatchError';
constructor({ expectedLength, givenLength, }) {
super([
'ABI encoding parameters/values length mismatch.',
`Expected length (parameters): ${expectedLength}`,
`Given length (values): ${givenLength}`,
].join('\n'));
}
}
/**
* The value provided is not a valid array as specified in the corresponding ABI parameter.
*
* ### Example
*
* ```ts twoslash
* // @noErrors
* import { AbiParameters } from 'ox'
* // ---cut---
* AbiParameters.encode(
* AbiParameters.from(['uint256[3]']),
* [69]
* )
* ```
*
* ### Solution
*
* Pass an array value.
*/
export class InvalidArrayError extends Errors.BaseError {
name = 'AbiParameters.InvalidArrayError';
constructor(value) {
super(`Value \`${value}\` is not a valid array.`);
}
}
/**
* Throws when the ABI parameter type is invalid.
*
* @example
* ```ts twoslash
* import { AbiParameters } from 'ox'
*
* AbiParameters.decode(
* [{ type: 'lol' }],
* '0x00000000000000000000000000000000000000000000000000000000000010f'
* )
* // ↑ ❌ invalid type
* // @error: AbiParameters.InvalidTypeError: Type `lol` is not a valid ABI Type.
* ```
*/
export class InvalidTypeError extends Errors.BaseError {
name = 'AbiParameters.InvalidTypeError';
constructor(type) {
super(`Type \`${type}\` is not a valid ABI Type.`);
}
}
//# sourceMappingURL=AbiParameters.js.map