UNPKG

ox

Version:

Ethereum Standard Library

422 lines 15.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.decodeParameter = decodeParameter; exports.decodeAddress = decodeAddress; exports.decodeArray = decodeArray; exports.decodeBool = decodeBool; exports.decodeBytes = decodeBytes; exports.decodeNumber = decodeNumber; exports.decodeTuple = decodeTuple; exports.decodeString = decodeString; exports.prepareParameters = prepareParameters; exports.prepareParameter = prepareParameter; exports.encode = encode; exports.encodeAddress = encodeAddress; exports.encodeArray = encodeArray; exports.encodeBytes = encodeBytes; exports.encodeBoolean = encodeBoolean; exports.encodeNumber = encodeNumber; exports.encodeString = encodeString; exports.encodeTuple = encodeTuple; exports.getArrayComponents = getArrayComponents; exports.hasDynamicChild = hasDynamicChild; const AbiParameters = require("../AbiParameters.js"); const Address = require("../Address.js"); const Bytes = require("../Bytes.js"); const Errors = require("../Errors.js"); const Hex = require("../Hex.js"); const Solidity_js_1 = require("../Solidity.js"); function decodeParameter(cursor, param, options) { const { checksumAddress, staticPosition } = options; const arrayComponents = getArrayComponents(param.type); if (arrayComponents) { const [length, type] = arrayComponents; return decodeArray(cursor, { ...param, type }, { checksumAddress, length, staticPosition }); } if (param.type === 'tuple') return decodeTuple(cursor, param, { checksumAddress, staticPosition, }); if (param.type === 'address') return decodeAddress(cursor, { checksum: checksumAddress }); if (param.type === 'bool') return decodeBool(cursor); if (param.type.startsWith('bytes')) return decodeBytes(cursor, param, { staticPosition }); if (param.type.startsWith('uint') || param.type.startsWith('int')) return decodeNumber(cursor, param); if (param.type === 'string') return decodeString(cursor, { staticPosition }); throw new AbiParameters.InvalidTypeError(param.type); } const sizeOfLength = 32; const sizeOfOffset = 32; function decodeAddress(cursor, options = {}) { const { checksum = false } = options; const value = cursor.readBytes(32); const wrap = (address) => checksum ? Address.checksum(address) : address; return [wrap(Hex.fromBytes(Bytes.slice(value, -20))), 32]; } function decodeArray(cursor, param, options) { const { checksumAddress, length, staticPosition } = options; if (!length) { const offset = Bytes.toNumber(cursor.readBytes(sizeOfOffset)); const start = staticPosition + offset; const startOfData = start + sizeOfLength; cursor.setPosition(start); const length = Bytes.toNumber(cursor.readBytes(sizeOfLength)); const dynamicChild = hasDynamicChild(param); let consumed = 0; const value = []; for (let i = 0; i < length; ++i) { cursor.setPosition(startOfData + (dynamicChild ? i * 32 : consumed)); const [data, consumed_] = decodeParameter(cursor, param, { checksumAddress, staticPosition: startOfData, }); consumed += consumed_; value.push(data); } cursor.setPosition(staticPosition + 32); return [value, 32]; } if (hasDynamicChild(param)) { const offset = Bytes.toNumber(cursor.readBytes(sizeOfOffset)); const start = staticPosition + offset; const value = []; for (let i = 0; i < length; ++i) { cursor.setPosition(start + i * 32); const [data] = decodeParameter(cursor, param, { checksumAddress, staticPosition: start, }); value.push(data); } cursor.setPosition(staticPosition + 32); return [value, 32]; } let consumed = 0; const value = []; for (let i = 0; i < length; ++i) { const [data, consumed_] = decodeParameter(cursor, param, { checksumAddress, staticPosition: staticPosition + consumed, }); consumed += consumed_; value.push(data); } return [value, consumed]; } function decodeBool(cursor) { return [Bytes.toBoolean(cursor.readBytes(32), { size: 32 }), 32]; } function decodeBytes(cursor, param, { staticPosition }) { const [_, size] = param.type.split('bytes'); if (!size) { const offset = Bytes.toNumber(cursor.readBytes(32)); cursor.setPosition(staticPosition + offset); const length = Bytes.toNumber(cursor.readBytes(32)); if (length === 0) { cursor.setPosition(staticPosition + 32); return ['0x', 32]; } const data = cursor.readBytes(length); cursor.setPosition(staticPosition + 32); return [Hex.fromBytes(data), 32]; } const value = Hex.fromBytes(cursor.readBytes(Number.parseInt(size), 32)); return [value, 32]; } function decodeNumber(cursor, param) { const signed = param.type.startsWith('int'); const size = Number.parseInt(param.type.split('int')[1] || '256'); const value = cursor.readBytes(32); return [ size > 48 ? Bytes.toBigInt(value, { signed }) : Bytes.toNumber(value, { signed }), 32, ]; } function decodeTuple(cursor, param, options) { const { checksumAddress, staticPosition } = options; const hasUnnamedChild = param.components.length === 0 || param.components.some(({ name }) => !name); const value = hasUnnamedChild ? [] : {}; let consumed = 0; if (hasDynamicChild(param)) { const offset = Bytes.toNumber(cursor.readBytes(sizeOfOffset)); const start = staticPosition + offset; for (let i = 0; i < param.components.length; ++i) { const component = param.components[i]; cursor.setPosition(start + consumed); const [data, consumed_] = decodeParameter(cursor, component, { checksumAddress, staticPosition: start, }); consumed += consumed_; value[hasUnnamedChild ? i : component?.name] = data; } cursor.setPosition(staticPosition + 32); return [value, 32]; } for (let i = 0; i < param.components.length; ++i) { const component = param.components[i]; const [data, consumed_] = decodeParameter(cursor, component, { checksumAddress, staticPosition, }); value[hasUnnamedChild ? i : component?.name] = data; consumed += consumed_; } return [value, consumed]; } function decodeString(cursor, { staticPosition }) { const offset = Bytes.toNumber(cursor.readBytes(32)); const start = staticPosition + offset; cursor.setPosition(start); const length = Bytes.toNumber(cursor.readBytes(32)); if (length === 0) { cursor.setPosition(staticPosition + 32); return ['', 32]; } const data = cursor.readBytes(length, 32); const value = Bytes.toString(Bytes.trimLeft(data)); cursor.setPosition(staticPosition + 32); return [value, 32]; } function prepareParameters({ checksumAddress, parameters, values, }) { const preparedParameters = []; for (let i = 0; i < parameters.length; i++) { preparedParameters.push(prepareParameter({ checksumAddress, parameter: parameters[i], value: values[i], })); } return preparedParameters; } function prepareParameter({ checksumAddress = false, parameter: parameter_, value, }) { const parameter = parameter_; const arrayComponents = getArrayComponents(parameter.type); if (arrayComponents) { const [length, type] = arrayComponents; return encodeArray(value, { checksumAddress, length, parameter: { ...parameter, type, }, }); } if (parameter.type === 'tuple') { return encodeTuple(value, { checksumAddress, parameter: parameter, }); } if (parameter.type === 'address') { return encodeAddress(value, { checksum: checksumAddress, }); } if (parameter.type === 'bool') { return encodeBoolean(value); } if (parameter.type.startsWith('uint') || parameter.type.startsWith('int')) { const signed = parameter.type.startsWith('int'); const [, , size = '256'] = Solidity_js_1.integerRegex.exec(parameter.type) ?? []; return encodeNumber(value, { signed, size: Number(size), }); } if (parameter.type.startsWith('bytes')) { return encodeBytes(value, { type: parameter.type }); } if (parameter.type === 'string') { return encodeString(value); } throw new AbiParameters.InvalidTypeError(parameter.type); } function encode(preparedParameters) { let staticSize = 0; for (let i = 0; i < preparedParameters.length; i++) { const { dynamic, encoded } = preparedParameters[i]; if (dynamic) staticSize += 32; else staticSize += Hex.size(encoded); } const staticParameters = []; const dynamicParameters = []; let dynamicSize = 0; for (let i = 0; i < preparedParameters.length; i++) { const { dynamic, encoded } = preparedParameters[i]; if (dynamic) { staticParameters.push(Hex.fromNumber(staticSize + dynamicSize, { size: 32 })); dynamicParameters.push(encoded); dynamicSize += Hex.size(encoded); } else { staticParameters.push(encoded); } } return Hex.concat(...staticParameters, ...dynamicParameters); } function encodeAddress(value, options) { const { checksum = false } = options; Address.assert(value, { strict: checksum }); return { dynamic: false, encoded: Hex.padLeft(value.toLowerCase()), }; } function encodeArray(value, options) { const { checksumAddress, length, parameter } = options; const dynamic = length === null; if (!Array.isArray(value)) throw new AbiParameters.InvalidArrayError(value); if (!dynamic && value.length !== length) throw new AbiParameters.ArrayLengthMismatchError({ expectedLength: length, givenLength: value.length, type: `${parameter.type}[${length}]`, }); let dynamicChild = false; const preparedParameters = []; for (let i = 0; i < value.length; i++) { const preparedParam = prepareParameter({ checksumAddress, parameter, value: value[i], }); if (preparedParam.dynamic) dynamicChild = true; preparedParameters.push(preparedParam); } if (dynamic || dynamicChild) { const data = encode(preparedParameters); if (dynamic) { const length = Hex.fromNumber(preparedParameters.length, { size: 32 }); return { dynamic: true, encoded: preparedParameters.length > 0 ? Hex.concat(length, data) : length, }; } if (dynamicChild) return { dynamic: true, encoded: data }; } return { dynamic: false, encoded: Hex.concat(...preparedParameters.map(({ encoded }) => encoded)), }; } function encodeBytes(value, { type }) { const [, parametersize] = type.split('bytes'); const bytesSize = Hex.size(value); if (!parametersize) { let value_ = value; if (bytesSize % 32 !== 0) value_ = Hex.padRight(value_, Math.ceil((value.length - 2) / 2 / 32) * 32); return { dynamic: true, encoded: Hex.concat(Hex.padLeft(Hex.fromNumber(bytesSize, { size: 32 })), value_), }; } if (bytesSize !== Number.parseInt(parametersize)) throw new AbiParameters.BytesSizeMismatchError({ expectedSize: Number.parseInt(parametersize), value, }); return { dynamic: false, encoded: Hex.padRight(value) }; } function encodeBoolean(value) { if (typeof value !== 'boolean') throw new Errors.BaseError(`Invalid boolean value: "${value}" (type: ${typeof value}). Expected: \`true\` or \`false\`.`); return { dynamic: false, encoded: Hex.padLeft(Hex.fromBoolean(value)) }; } function encodeNumber(value, { signed, size }) { if (typeof size === 'number') { const max = 2n ** (BigInt(size) - (signed ? 1n : 0n)) - 1n; const min = signed ? -max - 1n : 0n; if (value > max || value < min) throw new Hex.IntegerOutOfRangeError({ max: max.toString(), min: min.toString(), signed, size: size / 8, value: value.toString(), }); } return { dynamic: false, encoded: Hex.fromNumber(value, { size: 32, signed, }), }; } function encodeString(value) { const hexValue = Hex.fromString(value); const partsLength = Math.ceil(Hex.size(hexValue) / 32); const parts = []; for (let i = 0; i < partsLength; i++) { parts.push(Hex.padRight(Hex.slice(hexValue, i * 32, (i + 1) * 32))); } return { dynamic: true, encoded: Hex.concat(Hex.padRight(Hex.fromNumber(Hex.size(hexValue), { size: 32 })), ...parts), }; } function encodeTuple(value, options) { const { checksumAddress, parameter } = options; let dynamic = false; const preparedParameters = []; for (let i = 0; i < parameter.components.length; i++) { const param_ = parameter.components[i]; const index = Array.isArray(value) ? i : param_.name; const preparedParam = prepareParameter({ checksumAddress, parameter: param_, value: value[index], }); preparedParameters.push(preparedParam); if (preparedParam.dynamic) dynamic = true; } return { dynamic, encoded: dynamic ? encode(preparedParameters) : Hex.concat(...preparedParameters.map(({ encoded }) => encoded)), }; } function getArrayComponents(type) { const matches = type.match(/^(.*)\[(\d+)?\]$/); return matches ? [matches[2] ? Number(matches[2]) : null, matches[1]] : undefined; } function hasDynamicChild(param) { const { type } = param; if (type === 'string') return true; if (type === 'bytes') return true; if (type.endsWith('[]')) return true; if (type === 'tuple') return param.components?.some(hasDynamicChild); const arrayComponents = getArrayComponents(param.type); if (arrayComponents && hasDynamicChild({ ...param, type: arrayComponents[1], })) return true; return false; } //# sourceMappingURL=abiParameters.js.map