UNPKG

typechain

Version:

🔌 TypeScript bindings for Ethereum smartcontracts

322 lines • 13.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.isConstantFn = exports.isConstant = exports.ensure0xPrefix = exports.extractDocumentation = exports.extractBytecode = exports.extractAbi = exports.getFunctionDocumentation = exports.parseEvent = exports.parse = exports.parseContractPath = void 0; const js_sha3_1 = require("js-sha3"); const lodash_1 = require("lodash"); const path_1 = require("path"); const debug_1 = require("../utils/debug"); const errors_1 = require("../utils/errors"); const files_1 = require("../utils/files"); const normalizeName_1 = require("./normalizeName"); const parseEvmType_1 = require("./parseEvmType"); function parseContractPath(path) { const parsedPath = (0, path_1.parse)((0, files_1.normalizeSlashes)(path)); return { name: (0, normalizeName_1.normalizeName)(parsedPath.name), rawName: parsedPath.name, path: parsedPath.dir.split('/').filter((x) => x), }; } exports.parseContractPath = parseContractPath; function parse(abi, path, documentation) { const constructors = []; let fallback; const functions = []; const events = []; const structs = []; function registerStruct(newStruct) { var _a; // ignore registration if structName not present if (newStruct.structName === undefined) return; // if struct array (recursive) then keep going deep until we reach the struct tuple while (newStruct.type === 'array') { newStruct = newStruct.itemType; } // only register if not already registered const newStructName = (_a = newStruct.structName) === null || _a === void 0 ? void 0 : _a.toString(); if (!structs.find((s) => { var _a; return ((_a = s.structName) === null || _a === void 0 ? void 0 : _a.toString()) === newStructName; })) { structs.push(newStruct); } } abi.forEach((abiPiece) => { if (abiPiece.type === 'fallback') { if (fallback) { throw new Error(`Fallback function can't be defined more than once! ${JSON.stringify(abiPiece)} Previously defined: ${JSON.stringify(fallback)}`); } fallback = parseFallback(abiPiece, registerStruct); return; } if (abiPiece.type === 'constructor') { constructors.push(parseConstructor(abiPiece, registerStruct)); return; } if (abiPiece.type === 'function') { functions.push(parseFunctionDeclaration(abiPiece, registerStruct, documentation)); return; } if (abiPiece.type === 'event') { const eventAbi = abiPiece; events.push(parseEvent(eventAbi, registerStruct)); return; } (0, debug_1.debug)(`Unrecognized abi element: ${abiPiece.type}`); }); return { ...parseContractPath(path), fallback, constructor: constructors, functions: (0, lodash_1.groupBy)(functions, (f) => f.name), events: (0, lodash_1.groupBy)(events, (e) => e.name), structs: (0, lodash_1.groupBy)(structs, (e) => e.structName && e.structName.toString()), documentation: documentation ? (0, lodash_1.omit)(documentation, ['methods']) : undefined, }; } exports.parse = parse; function parseOutputs(registerStruct, outputs) { if (!outputs || outputs.length === 0) { return [{ name: '', type: { type: 'void' } }]; } else { return outputs.map(parseRawAbiParameter.bind(null, registerStruct)); } } function parseEvent(abiPiece, registerStruct) { var _a; (0, debug_1.debug)(`Parsing event "${abiPiece.name}"`); return { name: abiPiece.name, isAnonymous: (_a = abiPiece.anonymous) !== null && _a !== void 0 ? _a : false, inputs: abiPiece.inputs.map(parseRawEventArg.bind(null, registerStruct)), }; } exports.parseEvent = parseEvent; function parseRawEventArg(registerStruct, eventArg) { return { name: parseEmptyAsUndefined(eventArg.name), isIndexed: eventArg.indexed, type: parseRawAbiParameterType(eventArg, registerStruct), }; } function parseEmptyAsUndefined(smt) { if (smt === '') { return undefined; } return smt; } // if stateMutability is not available we will use old spec containing constant and payable function findStateMutability(abiPiece) { if (abiPiece.stateMutability) { return abiPiece.stateMutability; } if (abiPiece.constant) { return 'view'; } return abiPiece.payable ? 'payable' : 'nonpayable'; } function getFunctionDocumentation(abiPiece, documentation) { const docKey = `${abiPiece.name}(${abiPiece.inputs.map(({ type }) => type).join(',')})`; return documentation && documentation.methods && documentation.methods[docKey]; } exports.getFunctionDocumentation = getFunctionDocumentation; function parseConstructor(abiPiece, registerStruct) { (0, debug_1.debug)(`Parsing constructor declaration`); return { name: 'constructor', inputs: abiPiece.inputs.map(parseRawAbiParameter.bind(null, registerStruct)), outputs: [], stateMutability: findStateMutability(abiPiece), }; } function parseFallback(abiPiece, registerStruct) { (0, debug_1.debug)(`Parsing fallback declaration`); return { name: 'fallback', inputs: [], outputs: parseOutputs(registerStruct, abiPiece.outputs), stateMutability: findStateMutability(abiPiece), }; } function parseFunctionDeclaration(abiPiece, registerStruct, documentation) { (0, debug_1.debug)(`Parsing function declaration "${abiPiece.name}"`); return { name: abiPiece.name, inputs: abiPiece.inputs.map(parseRawAbiParameter.bind(null, registerStruct)), outputs: parseOutputs(registerStruct, abiPiece.outputs), stateMutability: findStateMutability(abiPiece), documentation: getFunctionDocumentation(abiPiece, documentation), }; } function parseRawAbiParameter(registerStruct, rawAbiParameter) { return { name: rawAbiParameter.name, type: parseRawAbiParameterType(rawAbiParameter, registerStruct), }; } const isStructType = (evmType) => evmType.type === 'array' || evmType.type === 'tuple'; function parseRawAbiParameterType(rawAbiParameter, registerStruct) { const components = rawAbiParameter.components && rawAbiParameter.components.map((component) => ({ name: component.name, type: parseRawAbiParameterType(component, registerStruct), })); const parsed = (0, parseEvmType_1.parseEvmType)(rawAbiParameter.type, components, rawAbiParameter.internalType); if (isStructType(parsed)) { if ('size' in parsed && parsed.size > 1 && isStructType(parsed.itemType) && parsed.structName) { // We unwrap constant size struct arrays like `Item[4]` into `Item`. registerStruct({ ...parsed.itemType, structName: parsed.structName.merge({ identifier: parsed.structName.identifier.replace(new RegExp(`\\[${parsed.size}\\]$`), ''), }), }); } else { registerStruct(parsed); } } return parsed; } function extractAbi(rawJson) { let json; try { json = JSON.parse(rawJson); } catch (_a) { throw new errors_1.MalformedAbiError('Not a json'); } if (!json) { throw new errors_1.MalformedAbiError('Not a json'); } if (Array.isArray(json)) { return json; } if (Array.isArray(json.abi)) { return json.abi; } else if (json.compilerOutput && Array.isArray(json.compilerOutput.abi)) { return json.compilerOutput.abi; } throw new errors_1.MalformedAbiError('Not a valid ABI'); } exports.extractAbi = extractAbi; function extractBytecode(rawContents) { var _a, _b, _c, _d, _e, _f; // When there are some unlinked libraries, the compiler replaces their addresses in calls with // "link references". There are many different kinds of those, depending on compiler version and usage. // Examples: // * `__TestLibrary___________________________` // (truffle with solc 0.4.x?, just the contract name) // * `__./ContractWithLibrary.sol:TestLibrar__` // (solc 0.4.x, `${fileName}:${contractName}` truncated at 36 chars) // * `__$8809803722eff063c8527a84f57d60014e$__` // (solc 0.5.x, ``solidityKeccak256(['string'], [`${fileName}:${contractName}`])``, truncated ) const bytecodeRegex = /^(0x)?(([0-9a-fA-F][0-9a-fA-F])|(__[a-zA-Z0-9/\\:_$.-]{36}__))+$/; // First try to see if this is a .bin file with just the bytecode, otherwise a json if (rawContents.match(bytecodeRegex)) return extractLinkReferences(rawContents); let json; try { json = JSON.parse(rawContents); } catch (_g) { return undefined; } if (!json) return undefined; function tryMatchBytecode(obj) { if (obj && obj.match instanceof Function) { return obj.match(bytecodeRegex); } } // `json.evm.bytecode` often has more information than `json.bytecode`, needs to be checked first if (tryMatchBytecode((_b = (_a = json.evm) === null || _a === void 0 ? void 0 : _a.bytecode) === null || _b === void 0 ? void 0 : _b.object)) { return extractLinkReferences(json.evm.bytecode.object, json.evm.bytecode.linkReferences); } // handle json schema of @0x/sol-compiler if (tryMatchBytecode((_e = (_d = (_c = json.compilerOutput) === null || _c === void 0 ? void 0 : _c.evm) === null || _d === void 0 ? void 0 : _d.bytecode) === null || _e === void 0 ? void 0 : _e.object)) { return extractLinkReferences(json.compilerOutput.evm.bytecode.object, json.compilerOutput.evm.bytecode.linkReferences); } // handle json schema of @foundry/forge if (tryMatchBytecode((_f = json.bytecode) === null || _f === void 0 ? void 0 : _f.object)) { return extractLinkReferences(json.bytecode.object, json.bytecode.linkReferences); } if (tryMatchBytecode(json.bytecode)) { return extractLinkReferences(json.bytecode, json.linkReferences); } return undefined; } exports.extractBytecode = extractBytecode; function extractDocumentation(rawContents) { let json; try { json = JSON.parse(rawContents); } catch (_a) { return undefined; } if (!json || (!json.devdoc && !json.userdoc)) return undefined; const result = json.devdoc || {}; // Merge devdoc and userdoc objects if (json.userdoc) { result.notice = json.userdoc.notice; if (!json.userdoc.methods) return result; result.methods = result.methods || {}; Object.entries(json.userdoc.methods).forEach(([key, { notice }]) => { if (result.methods) result.methods[key] = { ...result.methods[key], notice }; }); } return result; } exports.extractDocumentation = extractDocumentation; function extractLinkReferences(_bytecode, linkReferencesObj) { const bytecode = ensure0xPrefix(_bytecode); // See comment in `extractBytecode` for explanation. const allLinkReferencesRegex = /__[a-zA-Z0-9/\\:_$.-]{36}__/g; const allReferences = bytecode.match(allLinkReferencesRegex); if (!allReferences) return { bytecode }; const uniqueReferences = Array.from(new Set(allReferences)); const refToNameMap = linkReferencesObj ? extractLinkReferenceContractNames(linkReferencesObj) : {}; const linkReferences = uniqueReferences.map((reference) => refToNameMap[reference] ? { reference, name: refToNameMap[reference] } : { reference }); return { bytecode, linkReferences }; } // Returns mapping from link reference (bytecode fake address) to readable contract name function extractLinkReferenceContractNames(linkReferences) { // `evm.bytecode.linkReferences` example: // { // "ContractWithLibrary.sol": { // "TestLibrary": [ // { "length": 20, "start": 151 }, // { "length": 20, "start": 177 }, // ], // }, // }, const nameMap = {}; Object.keys(linkReferences).forEach((contractFile) => Object.keys(linkReferences[contractFile]).forEach((contractName) => { const contractPath = `${contractFile}:${contractName}`; const contractRef = `__$${(0, js_sha3_1.keccak_256)(contractPath).slice(0, 34)}$__`; nameMap[contractRef] = contractPath; })); return nameMap; } function ensure0xPrefix(hexString) { if (hexString.startsWith('0x')) return hexString; return '0x' + hexString; } exports.ensure0xPrefix = ensure0xPrefix; function isConstant(fn) { return ((fn.stateMutability === 'pure' || fn.stateMutability === 'view') && fn.inputs.length === 0 && fn.outputs.length === 1); } exports.isConstant = isConstant; function isConstantFn(fn) { return (fn.stateMutability === 'pure' || fn.stateMutability === 'view') && !isConstant(fn); } exports.isConstantFn = isConstantFn; //# sourceMappingURL=abiParser.js.map