UNPKG

ox

Version:

Ethereum Standard Library

532 lines 16 kB
import * as abitype from 'abitype'; 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 Solidity from './Solidity.js'; import * as internal from './internal/abiParameters.js'; import * as Cursor from './internal/cursor.js'; // eslint-disable-next-line jsdoc/require-jsdoc export function decode(parameters, data, options = {}) { const { as = 'Array', checksumAddress = false } = 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]; 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; } 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/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) / 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) !== (value.length - 2) / 2) throw new BytesSizeMismatchError({ expectedSize: Number.parseInt(size), value: value, }); return Hex.padRight(value, isArray ? 32 : 0); } const arrayMatch = type.match(Solidity.arrayRegex); if (arrayMatch && Array.isArray(value)) { const [_type, childType] = arrayMatch; 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 abitype.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 abitype.parseAbiParameters(parameters); if (typeof parameters === 'string') return abitype.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 { constructor({ data, parameters, size, }) { super(`Data size of ${size} bytes is too small for given parameters.`, { metaMessages: [ `Params: (${abitype.formatAbiParameters(parameters)})`, `Data: ${data} (${size} bytes)`, ], }); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.DataSizeTooSmallError' }); } } /** * 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 { constructor() { super('Cannot decode zero data ("0x") with ABI parameters.'); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.ZeroDataError' }); } } /** * 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 { constructor({ expectedLength, givenLength, type, }) { super(`Array length mismatch for type \`${type}\`. Expected: \`${expectedLength}\`. Given: \`${givenLength}\`.`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.ArrayLengthMismatchError' }); } } /** * 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 { constructor({ expectedSize, value, }) { super(`Size of bytes "${value}" (bytes${Hex.size(value)}) does not match expected size (bytes${expectedSize}).`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.BytesSizeMismatchError' }); } } /** * 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 { constructor({ expectedLength, givenLength, }) { super([ 'ABI encoding parameters/values length mismatch.', `Expected length (parameters): ${expectedLength}`, `Given length (values): ${givenLength}`, ].join('\n')); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.LengthMismatchError' }); } } /** * 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 { constructor(value) { super(`Value \`${value}\` is not a valid array.`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.InvalidArrayError' }); } } /** * 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 { constructor(type) { super(`Type \`${type}\` is not a valid ABI Type.`); Object.defineProperty(this, "name", { enumerable: true, configurable: true, writable: true, value: 'AbiParameters.InvalidTypeError' }); } } //# sourceMappingURL=AbiParameters.js.map