diff --git a/README.md b/README.md index 00e35892..c181c674 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Twitter Follow](https://img.shields.io/twitter/follow/naddison?style=social)](https://twitter.com/naddison) A visualisation tool for [Solidity](https://solidity.readthedocs.io/) contracts featuring: + 1. [Unified Modeling Language (UML)](https://en.wikipedia.org/wiki/Unified_Modeling_Language) [class diagram](https://en.wikipedia.org/wiki/Class_diagram) generator for Solidity contracts. 2. Contract storage layout diagrams. 3. Flatten Solidity files on Etherscan-like explorers to a local file. @@ -71,10 +72,10 @@ Options: Commands: class [options] Generates a UML class diagram from Solidity source code. storage [options] Visually display a contract's storage slots. - + WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized. flatten Merges verified source files for a contract from a Blockchain explorer into one local Solidity file. - + In order for the merged code to compile, the following is done: 1. pragma solidity is set using the compiler of the verified contract. 2. All pragma solidity lines in the source files are commented out. @@ -82,7 +83,7 @@ Commands: 4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier". 5. Contract dependencies are analysed so the files are merged in an order that will compile. diff [options] Compare verified Solidity code to another verified contract, a local file or local source files. - + The results show the comparison of contract A to B. The green sections are additions to contract B that are not in contract A. The red sections are removals from contract A that are not in contract B. @@ -315,11 +316,12 @@ Other color formats like Red-Green-Blue (RGB) can also be used. For example, #ff See [Graphviz color](https://graphviz.org/docs/attr-types/color/) documentation for more details. Here's an example using the color options + ``` sol2uml storage -sc deeppink -tc #ffffff -fc dimgrey -bc black 0xfCc00A1e250644d89AF0df661bC6f04891E21585 ``` -![Aave V3 Pool](./examples/storage/AaveV3PoolStorageColor.svg ) +![Aave V3 Pool](./examples/storage/AaveV3PoolStorageColor.svg) # Version 2.x changes diff --git a/bonk.yaml b/bonk.yaml index 21ee3126..a66926da 100644 --- a/bonk.yaml +++ b/bonk.yaml @@ -1 +1 @@ -echo Hello World! \ No newline at end of file +echo Hello World! diff --git a/examples/accountAbstraction/README.md b/examples/accountAbstraction/README.md index 0cdbf183..18f7143f 100644 --- a/examples/accountAbstraction/README.md +++ b/examples/accountAbstraction/README.md @@ -4,14 +4,15 @@ The following sol2uml diagrams have been run against the [ERC-4337](https://eips [eth-infinitism/account-abstraction](https://github.com/eth-infinitism/account-abstraction) GitHub repository. The main contracts are -* [contracts/core/IAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/IAccount.sol) -* [contracts/samples/SimpleAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) -* [contracts/core/EntryPoint.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol) +- [contracts/core/IAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/interfaces/IAccount.sol) +- [contracts/samples/SimpleAccount.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccount.sol) +- [contracts/core/EntryPoint.sol](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/core/EntryPoint.sol) The full option names have been used below rather than the short names for readability. For example, `--baseContractNames` instead of just `-b`. To run the following, set the `AA` environment variable to the location of the Account Abstraction contracts. For example + ```sh export AA=../../../account-abstraction/contracts ``` @@ -48,7 +49,6 @@ sol2uml class $AA --baseContractNames SimpleAccount --squash --depth 0 --outputF ![Simple Account Squashed](./SimpleAccountSquashed.svg) - ## Simple Account ```sh diff --git a/examples/storage/README.md b/examples/storage/README.md index b5689a0e..18205c1d 100644 --- a/examples/storage/README.md +++ b/examples/storage/README.md @@ -152,12 +152,11 @@ sol2uml storage ./src/contracts -c StructStorage ![StructStorage](./StructStorage.svg) -The first `exampleStruct` variables is of `ExampleStruct` type. sol2uml will display how many slots the struct uses and then reference an expanded view of how the struct variables are stored in the slots. +The first `exampleStruct` variables is of `ExampleStruct` type. sol2uml will display how many slots the struct uses and then reference an expanded view of how the struct variables are stored in the slots. If any of the struct variables are arrays, strings, bytes or other structs, they will recursively be referenced until the elementary types are reached. The second `dynamicStructs` variable is a dynamic array of type `ExampleStruct`. When sol2uml is run without the `-d, --data` option, it does not know how long the array is so will just display what the first array item would look like. - The following is generated from the `StructStorage` contract deployed on Arbitrum to [0xB8F98C34e40E0D201CE2F3440cE92d0B5c5CfFe2](https://arbiscan.io/address/0xB8F98C34e40E0D201CE2F3440cE92d0B5c5CfFe2#code). ``` @@ -191,13 +190,13 @@ Variables `uninitString` and `emptyString` have the same slot values of zero byt The `name` variable with a 22 character string fits in a single slot. The [UTF-8](https://en.wikipedia.org/wiki/UTF-8) encoded string is stored from right to left. The last byte on the right is the length of the string that is left-bit shifted. Mathematically, the length is multiplied by 2. -So the 22 character string becomes 22 * 2 = 44 which is 2C in hexadecimal. +So the 22 character string becomes 22 \* 2 = 44 which is 2C in hexadecimal. The `long2` variable has a string that is 59 characters long. As it is greater than 31 bytes, it can't fit in slot 5 which the variable is assigned. Slot 5 contains the length of the string that is left-bit shifted and the last bit set to 1. Mathematically, the length is multiplied by 2 and 1 is added. -So the encoded length of the `long2` variable becomes 59 * 2 + 1 = 119 which is 0x77 in hexadecimal format. +So the encoded length of the `long2` variable becomes 59 \* 2 + 1 = 119 which is 0x77 in hexadecimal format. sol2uml will display the decoded string lengths when strings are greater than 31 bytes and the string when they are less than 32 bytes. If the rightmost bit of a string variable's slot is set to 1 then the string is dynamically stored in another location and then the slot just contains the encoded string length. @@ -290,7 +289,6 @@ sol2uml storage 0x2fdfbb2b905484f1445e23a97c97f65fe0e43dec -v \ -o examples/storage/origin-oeth-dripper-hide-values.svg ``` - ## USDC The USD Coin (USDC) token deployed to [0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48](https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48#code) on mainnet is a proxied contract. diff --git a/lib/SlotValueCache.d.ts b/lib/SlotValueCache.d.ts index ccd0d935..d1abd3e9 100644 --- a/lib/SlotValueCache.d.ts +++ b/lib/SlotValueCache.d.ts @@ -1,19 +1,19 @@ -import { BigNumberish } from '@ethersproject/bignumber'; +import { BigNumberish } from '@ethersproject/bignumber' /** * Singleton that caches a mapping of slot keys to values. * Assumes all data is read from the same block and contract */ export declare class SlotValueCache { - private static slotCache; + private static slotCache /** * @param slotKeys array of slot numbers or slot keys in hexadecimal format * @return cachedValues array of the slot values that are in the cache. * @return missingKeys array of the slot keys that are not cached in hexadecimal format. */ static readSlotValues(slotKeys: readonly BigNumberish[]): { - cachedValues: string[]; - missingKeys: string[]; - }; + cachedValues: string[] + missingKeys: string[] + } /** * Adds the missing slot values to the cache and then returns all slot values from * the cache for each of the `slotKeys`. @@ -22,10 +22,14 @@ export declare class SlotValueCache { * @param missingValues array of slot values in hexadecimal format. * @return values array of slot values for each of the `slotKeys`. */ - static addSlotValues(slotKeys: readonly BigNumberish[], missingKeys: readonly string[], missingValues: readonly string[]): string[]; + static addSlotValues( + slotKeys: readonly BigNumberish[], + missingKeys: readonly string[], + missingValues: readonly string[], + ): string[] /** * Used for testing purposes to clear the cache. * This allows tests to run against different contracts and blockTags */ - static clear(): void; + static clear(): void } diff --git a/lib/SlotValueCache.js b/lib/SlotValueCache.js index b7e46b1e..5b4676e0 100644 --- a/lib/SlotValueCache.js +++ b/lib/SlotValueCache.js @@ -1,8 +1,8 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.SlotValueCache = void 0; -const bignumber_1 = require("@ethersproject/bignumber"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.SlotValueCache = void 0 +const bignumber_1 = require('@ethersproject/bignumber') +const debug = require('debug')('sol2uml') /** * Singleton that caches a mapping of slot keys to values. * Assumes all data is read from the same block and contract @@ -14,19 +14,19 @@ class SlotValueCache { * @return missingKeys array of the slot keys that are not cached in hexadecimal format. */ static readSlotValues(slotKeys) { - const cachedValues = []; - const missingKeys = []; + const cachedValues = [] + const missingKeys = [] slotKeys.forEach((slotKey, i) => { - const key = bignumber_1.BigNumber.from(slotKey).toHexString(); + const key = bignumber_1.BigNumber.from(slotKey).toHexString() if (this.slotCache[key]) { - cachedValues.push(this.slotCache[key]); + cachedValues.push(this.slotCache[key]) + } else { + missingKeys.push(key) } - else { - missingKeys.push(key); - } - }); - return { cachedValues, missingKeys }; + }) + return { cachedValues, missingKeys } } + /** * Adds the missing slot values to the cache and then returns all slot values from * the cache for each of the `slotKeys`. @@ -37,29 +37,32 @@ class SlotValueCache { */ static addSlotValues(slotKeys, missingKeys, missingValues) { if (missingKeys?.length !== missingValues?.length) { - throw Error(`${missingKeys?.length} keys does not match ${missingValues?.length} values`); + throw Error( + `${missingKeys?.length} keys does not match ${missingValues?.length} values`, + ) } missingKeys.forEach((key, i) => { if (!this.slotCache[key]) { - debug(`cached slot ${key} with ${missingValues[i]}`); - this.slotCache[key] = missingValues[i]; + debug(`cached slot ${key} with ${missingValues[i]}`) + this.slotCache[key] = missingValues[i] } - }); + }) return slotKeys.map((slotKey) => { - const key = bignumber_1.BigNumber.from(slotKey).toHexString(); + const key = bignumber_1.BigNumber.from(slotKey).toHexString() // it should find the slot value in the cache. if not it'll return undefined - return this.slotCache[key]; - }); + return this.slotCache[key] + }) } + /** * Used for testing purposes to clear the cache. * This allows tests to run against different contracts and blockTags */ static clear() { - this.slotCache = {}; + this.slotCache = {} } } -exports.SlotValueCache = SlotValueCache; +exports.SlotValueCache = SlotValueCache // Singleton of cached slot keys mapped to values -SlotValueCache.slotCache = {}; -//# sourceMappingURL=SlotValueCache.js.map \ No newline at end of file +SlotValueCache.slotCache = {} +// # sourceMappingURL=SlotValueCache.js.map diff --git a/lib/associations.d.ts b/lib/associations.d.ts index 5501a215..d9db09ce 100644 --- a/lib/associations.d.ts +++ b/lib/associations.d.ts @@ -1,2 +1,7 @@ -import { Association, UmlClass } from './umlClass'; -export declare const findAssociatedClass: (association: Association, sourceUmlClass: UmlClass, umlClasses: readonly UmlClass[], searchedAbsolutePaths?: string[]) => UmlClass | undefined; +import { Association, UmlClass } from './umlClass' +export declare const findAssociatedClass: ( + association: Association, + sourceUmlClass: UmlClass, + umlClasses: readonly UmlClass[], + searchedAbsolutePaths?: string[], +) => UmlClass | undefined diff --git a/lib/associations.js b/lib/associations.js index 5363a9cf..c23d38f3 100644 --- a/lib/associations.js +++ b/lib/associations.js @@ -1,147 +1,226 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.findAssociatedClass = void 0; +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.findAssociatedClass = void 0 // Find the UML class linked to the association -const findAssociatedClass = (association, sourceUmlClass, umlClasses, searchedAbsolutePaths = []) => { +const findAssociatedClass = ( + association, + sourceUmlClass, + umlClasses, + searchedAbsolutePaths = [], +) => { const umlClass = umlClasses.find((targetUmlClass) => { - const targetParentClass = association.parentUmlClassName && + const targetParentClass = + association.parentUmlClassName && targetUmlClass.parentId !== undefined - ? umlClasses[targetUmlClass.parentId] - : undefined; - return isAssociated(association, sourceUmlClass, targetUmlClass, targetParentClass); - }); + ? umlClasses[targetUmlClass.parentId] + : undefined + return isAssociated( + association, + sourceUmlClass, + targetUmlClass, + targetParentClass, + ) + }) // If a link was found - if (umlClass) - return umlClass; + if (umlClass) { + return umlClass + } // Could not find association so now need to recursively look at imports of imports // add to already recursively processed files to avoid getting stuck in circular imports - searchedAbsolutePaths.push(sourceUmlClass.absolutePath); - const importedType = findChainedImport(association, sourceUmlClass, umlClasses, searchedAbsolutePaths); - if (importedType) - return importedType; + searchedAbsolutePaths.push(sourceUmlClass.absolutePath) + const importedType = findChainedImport( + association, + sourceUmlClass, + umlClasses, + searchedAbsolutePaths, + ) + if (importedType) { + return importedType + } // Still could not find association so now need to recursively look for inherited types - const inheritedType = findInheritedType(association, sourceUmlClass, umlClasses); - if (inheritedType) - return inheritedType; - return undefined; -}; -exports.findAssociatedClass = findAssociatedClass; + const inheritedType = findInheritedType( + association, + sourceUmlClass, + umlClasses, + ) + if (inheritedType) { + return inheritedType + } + return undefined +} +exports.findAssociatedClass = findAssociatedClass // Tests if source class can be linked to the target class via an association -const isAssociated = (association, sourceUmlClass, targetUmlClass, targetParentUmlClass) => { +const isAssociated = ( + association, + sourceUmlClass, + targetUmlClass, + targetParentUmlClass, +) => { if (association.parentUmlClassName) { return ( - // class is in the same source file - (association.targetUmlClassName === targetUmlClass.name && - association.parentUmlClassName === targetParentUmlClass?.name && - sourceUmlClass.absolutePath === targetUmlClass.absolutePath) || + // class is in the same source file + (association.targetUmlClassName === targetUmlClass.name && + association.parentUmlClassName === targetParentUmlClass?.name && + sourceUmlClass.absolutePath === targetUmlClass.absolutePath) || // imported classes with no explicit import names (association.targetUmlClassName === targetUmlClass.name && association.parentUmlClassName === targetParentUmlClass?.name && - sourceUmlClass.imports.some((i) => i.absolutePath === targetUmlClass.absolutePath && - i.classNames.length === 0)) || + sourceUmlClass.imports.some( + (i) => + i.absolutePath === targetUmlClass.absolutePath && + i.classNames.length === 0, + )) || // imported classes with explicit import names or import aliases - sourceUmlClass.imports.some((importLink) => importLink.absolutePath === targetUmlClass.absolutePath && - importLink.classNames.some((importedClass) => - // If a parent contract with no import alias - (association.targetUmlClassName === - targetUmlClass.name && - association.parentUmlClassName === - importedClass.className && - importedClass.alias == undefined) || - // If a parent contract with import alias - (association.targetUmlClassName === - targetUmlClass.name && - association.parentUmlClassName === - importedClass.alias)))); + sourceUmlClass.imports.some( + (importLink) => + importLink.absolutePath === targetUmlClass.absolutePath && + importLink.classNames.some( + (importedClass) => + // If a parent contract with no import alias + (association.targetUmlClassName === + targetUmlClass.name && + association.parentUmlClassName === + importedClass.className && + importedClass.alias == undefined) || + // If a parent contract with import alias + (association.targetUmlClassName === + targetUmlClass.name && + association.parentUmlClassName === + importedClass.alias), + ), + ) + ) } // No parent class in the association return ( - // class is in the same source file - (association.targetUmlClassName === targetUmlClass.name && - sourceUmlClass.absolutePath === targetUmlClass.absolutePath) || + // class is in the same source file + (association.targetUmlClassName === targetUmlClass.name && + sourceUmlClass.absolutePath === targetUmlClass.absolutePath) || // imported classes with no explicit import names (association.targetUmlClassName === targetUmlClass.name && - sourceUmlClass.imports.some((i) => i.absolutePath === targetUmlClass.absolutePath && - i.classNames.length === 0)) || + sourceUmlClass.imports.some( + (i) => + i.absolutePath === targetUmlClass.absolutePath && + i.classNames.length === 0, + )) || // imported classes with explicit import names or import aliases - sourceUmlClass.imports.some((importLink) => importLink.absolutePath === targetUmlClass.absolutePath && - importLink.classNames.some((importedClass) => - // no import alias - (association.targetUmlClassName === - importedClass.className && - importedClass.className === targetUmlClass.name && - importedClass.alias == undefined) || - // import alias - (association.targetUmlClassName === - importedClass.alias && - importedClass.className === targetUmlClass.name)))); -}; + sourceUmlClass.imports.some( + (importLink) => + importLink.absolutePath === targetUmlClass.absolutePath && + importLink.classNames.some( + (importedClass) => + // no import alias + (association.targetUmlClassName === + importedClass.className && + importedClass.className === targetUmlClass.name && + importedClass.alias == undefined) || + // import alias + (association.targetUmlClassName === + importedClass.alias && + importedClass.className === targetUmlClass.name), + ), + ) + ) +} const findInheritedType = (association, sourceUmlClass, umlClasses) => { // Get all realized associations. - const parentAssociations = sourceUmlClass.getParentContracts(); + const parentAssociations = sourceUmlClass.getParentContracts() // For each parent association for (const parentAssociation of parentAssociations) { - const parent = (0, exports.findAssociatedClass)(parentAssociation, sourceUmlClass, umlClasses); - if (!parent) - continue; + const parent = (0, exports.findAssociatedClass)( + parentAssociation, + sourceUmlClass, + umlClasses, + ) + if (!parent) { + continue + } // For each struct on the parent for (const structId of parent.structs) { - const structUmlClass = umlClasses.find((c) => c.id === structId); - if (!structUmlClass) - continue; + const structUmlClass = umlClasses.find((c) => c.id === structId) + if (!structUmlClass) { + continue + } if (structUmlClass.name === association.targetUmlClassName) { - return structUmlClass; + return structUmlClass } } // For each enum on the parent for (const enumId of parent.enums) { - const enumUmlClass = umlClasses.find((c) => c.id === enumId); - if (!enumUmlClass) - continue; + const enumUmlClass = umlClasses.find((c) => c.id === enumId) + if (!enumUmlClass) { + continue + } if (enumUmlClass.name === association.targetUmlClassName) { - return enumUmlClass; + return enumUmlClass } } // Recursively look for inherited types - const targetClass = findInheritedType(association, parent, umlClasses); - if (targetClass) - return targetClass; + const targetClass = findInheritedType(association, parent, umlClasses) + if (targetClass) { + return targetClass + } } - return undefined; -}; -const findChainedImport = (association, sourceUmlClass, umlClasses, searchedRelativePaths) => { + return undefined +} +const findChainedImport = ( + association, + sourceUmlClass, + umlClasses, + searchedRelativePaths, +) => { // Get all valid imports. That is, imports that do not explicitly import contracts or interfaces // or explicitly import the source class - const imports = sourceUmlClass.imports.filter((i) => i.classNames.length === 0 || - i.classNames.some((cn) => (association.targetUmlClassName === cn.className && - !cn.alias) || - association.targetUmlClassName === cn.alias)); + const imports = sourceUmlClass.imports.filter( + (i) => + i.classNames.length === 0 || + i.classNames.some( + (cn) => + (association.targetUmlClassName === cn.className && + !cn.alias) || + association.targetUmlClassName === cn.alias, + ), + ) // For each import for (const importDetail of imports) { // Find a class with the same absolute path as the import so we can get the new imports - const newSourceUmlClass = umlClasses.find((c) => c.absolutePath === importDetail.absolutePath); + const newSourceUmlClass = umlClasses.find( + (c) => c.absolutePath === importDetail.absolutePath, + ) if (!newSourceUmlClass) { // Could not find a class in the import file so just move onto the next loop - continue; + continue } // Avoid circular imports if (searchedRelativePaths.includes(newSourceUmlClass.absolutePath)) { // Have already recursively looked for imports of imports in this file - continue; + continue } // find class linked to the association without aliased imports - const umlClass = (0, exports.findAssociatedClass)(association, newSourceUmlClass, umlClasses, searchedRelativePaths); - if (umlClass) - return umlClass; + const umlClass = (0, exports.findAssociatedClass)( + association, + newSourceUmlClass, + umlClasses, + searchedRelativePaths, + ) + if (umlClass) { + return umlClass + } // find all aliased imports - const aliasedImports = importDetail.classNames.filter((cn) => cn.alias); + const aliasedImports = importDetail.classNames.filter((cn) => cn.alias) // For each aliased import for (const aliasedImport of aliasedImports) { - const umlClass = (0, exports.findAssociatedClass)({ ...association, targetUmlClassName: aliasedImport.className }, newSourceUmlClass, umlClasses, searchedRelativePaths); - if (umlClass) - return umlClass; + const umlClass = (0, exports.findAssociatedClass)( + { ...association, targetUmlClassName: aliasedImport.className }, + newSourceUmlClass, + umlClasses, + searchedRelativePaths, + ) + if (umlClass) { + return umlClass + } } } - return undefined; -}; -//# sourceMappingURL=associations.js.map \ No newline at end of file + return undefined +} +// # sourceMappingURL=associations.js.map diff --git a/lib/converterAST2Classes.d.ts b/lib/converterAST2Classes.d.ts index 85586708..3c45ac67 100644 --- a/lib/converterAST2Classes.d.ts +++ b/lib/converterAST2Classes.d.ts @@ -1,6 +1,6 @@ -import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types'; -import { UmlClass } from './umlClass'; -import { Remapping } from './parserEtherscan'; +import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types' +import { UmlClass } from './umlClass' +import { Remapping } from './parserEtherscan' /** * Convert solidity parser output of type `ASTNode` to UML classes of type `UMLClass` * @param node output of Solidity parser of type `ASTNode` @@ -9,7 +9,12 @@ import { Remapping } from './parserEtherscan'; * @param filesystem flag if Solidity source code was parsed from the filesystem or Etherscan * @return umlClasses array of UML class definitions of type `UmlClass` */ -export declare function convertAST2UmlClasses(node: ASTNode, relativePath: string, remappings: Remapping[], filesystem?: boolean): UmlClass[]; +export declare function convertAST2UmlClasses( + node: ASTNode, + relativePath: string, + remappings: Remapping[], + filesystem?: boolean, +): UmlClass[] /** * Used to rename import file names. For example * @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol @@ -18,4 +23,7 @@ export declare function convertAST2UmlClasses(node: ASTNode, relativePath: strin * @param fileName file name in the Solidity code * @param mappings an array of remappings from Etherscan's settings */ -export declare const renameFile: (fileName: string, mappings: Remapping[]) => string; +export declare const renameFile: ( + fileName: string, + mappings: Remapping[], +) => string diff --git a/lib/converterAST2Classes.js b/lib/converterAST2Classes.js index 17ce1a55..a04c85b4 100644 --- a/lib/converterAST2Classes.js +++ b/lib/converterAST2Classes.js @@ -1,35 +1,67 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; +'use strict' +const __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k + let desc = Object.getOwnPropertyDescriptor(m, k) + if ( + !desc || + ('get' in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k] + }, + } + } + Object.defineProperty(o, k2, desc) + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k + o[k2] = m[k] + }) +const __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { + enumerable: true, + value: v, + }) + } + : function (o, v) { + o.default = v + }) +const __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod + const result = {} + if (mod != null) { + for (const k in mod) { + if ( + k !== 'default' && + Object.prototype.hasOwnProperty.call(mod, k) + ) { + __createBinding(result, mod, k) + } + } + } + __setModuleDefault(result, mod) + return result } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.renameFile = exports.convertAST2UmlClasses = void 0; -const path = __importStar(require("path")); -const path_1 = require("path"); -const umlClass_1 = require("./umlClass"); -const typeGuards_1 = require("./typeGuards"); -const debug = require('debug')('sol2uml'); -let umlClasses; +Object.defineProperty(exports, '__esModule', { value: true }) +exports.renameFile = exports.convertAST2UmlClasses = void 0 +const path = __importStar(require('path')) +const path_1 = require('path') +const umlClass_1 = require('./umlClass') +const typeGuards_1 = require('./typeGuards') +const debug = require('debug')('sol2uml') +let umlClasses /** * Convert solidity parser output of type `ASTNode` to UML classes of type `UMLClass` * @param node output of Solidity parser of type `ASTNode` @@ -38,106 +70,122 @@ let umlClasses; * @param filesystem flag if Solidity source code was parsed from the filesystem or Etherscan * @return umlClasses array of UML class definitions of type `UmlClass` */ -function convertAST2UmlClasses(node, relativePath, remappings, filesystem = false) { - const imports = []; - umlClasses = []; +function convertAST2UmlClasses( + node, + relativePath, + remappings, + filesystem = false, +) { + const imports = [] + umlClasses = [] if (node.type === 'SourceUnit') { node.children.forEach((childNode) => { if (childNode.type === 'ContractDefinition') { - let umlClass = new umlClass_1.UmlClass({ + const umlClass = new umlClass_1.UmlClass({ name: childNode.name, absolutePath: filesystem ? path.resolve(relativePath) // resolve the absolute path : relativePath, relativePath, - }); - umlClasses.push(umlClass); - parseContractDefinition(childNode, umlClass); - debug(`Added contract ${childNode.name}`); - } - else if (childNode.type === 'StructDefinition') { - debug(`Adding file level struct ${childNode.name}`); - let umlClass = new umlClass_1.UmlClass({ + }) + umlClasses.push(umlClass) + parseContractDefinition(childNode, umlClass) + debug(`Added contract ${childNode.name}`) + } else if (childNode.type === 'StructDefinition') { + debug(`Adding file level struct ${childNode.name}`) + const umlClass = new umlClass_1.UmlClass({ name: childNode.name, stereotype: umlClass_1.ClassStereotype.Struct, absolutePath: filesystem ? path.resolve(relativePath) // resolve the absolute path : relativePath, relativePath, - }); - parseStructDefinition(childNode, umlClass); - debug(`Added struct ${umlClass.name}`); - umlClasses.push(umlClass); - } - else if (childNode.type === 'EnumDefinition') { - debug(`Adding file level enum ${childNode.name}`); - let umlClass = new umlClass_1.UmlClass({ + }) + parseStructDefinition(childNode, umlClass) + debug(`Added struct ${umlClass.name}`) + umlClasses.push(umlClass) + } else if (childNode.type === 'EnumDefinition') { + debug(`Adding file level enum ${childNode.name}`) + const umlClass = new umlClass_1.UmlClass({ name: childNode.name, stereotype: umlClass_1.ClassStereotype.Enum, absolutePath: filesystem ? path.resolve(relativePath) // resolve the absolute path : relativePath, relativePath, - }); - debug(`Added enum ${umlClass.name}`); - parseEnumDefinition(childNode, umlClass); - umlClasses.push(umlClass); - } - else if (childNode.type === 'ImportDirective') { - const codeFolder = path.dirname(relativePath); + }) + debug(`Added enum ${umlClass.name}`) + parseEnumDefinition(childNode, umlClass) + umlClasses.push(umlClass) + } else if (childNode.type === 'ImportDirective') { + const codeFolder = path.dirname(relativePath) if (filesystem) { // resolve the imported file from the folder sol2uml was run against try { const importPath = require.resolve(childNode.path, { paths: [codeFolder], - }); + }) const newImport = { absolutePath: importPath, classNames: childNode.symbolAliases ? childNode.symbolAliases.map((alias) => { - return { - className: alias[0], - alias: alias[1], - }; - }) + return { + className: alias[0], + alias: alias[1], + } + }) : [], - }; - debug(`Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames.map((i) => i.className)}`); - imports.push(newImport); + } + debug( + `Added filesystem import ${newImport.absolutePath} with class names ${newImport.classNames.map((i) => i.className)}`, + ) + imports.push(newImport) + } catch (err) { + debug( + `Failed to resolve import ${childNode.path} from file ${relativePath}`, + ) } - catch (err) { - debug(`Failed to resolve import ${childNode.path} from file ${relativePath}`); - } - } - else { + } else { // this has come from Etherscan - const remappedFile = (0, exports.renameFile)(childNode.path, remappings); - const importPath = remappedFile[0] === '.' - ? // Use Linux paths, not Windows paths, to resolve Etherscan files - path_1.posix.join(codeFolder.toString(), remappedFile) - : remappedFile; - debug(`codeFolder ${codeFolder} childNode.path ${childNode.path} remapped to ${remappedFile}`); + const remappedFile = (0, exports.renameFile)( + childNode.path, + remappings, + ) + const importPath = + remappedFile[0] === '.' + ? // Use Linux paths, not Windows paths, to resolve Etherscan files + path_1.posix.join( + codeFolder.toString(), + remappedFile, + ) + : remappedFile + debug( + `codeFolder ${codeFolder} childNode.path ${childNode.path} remapped to ${remappedFile}`, + ) const newImport = { absolutePath: importPath, classNames: childNode.symbolAliases ? childNode.symbolAliases.map((alias) => { - return { - className: alias[0], - alias: alias[1], - }; - }) + return { + className: alias[0], + alias: alias[1], + } + }) : [], - }; - debug(`Added Etherscan import ${newImport.absolutePath} with:`); + } + debug( + `Added Etherscan import ${newImport.absolutePath} with:`, + ) newImport.classNames.forEach((className) => { - debug(`\t alias ${className.className}, name ${className.className}`); - }); - imports.push(newImport); + debug( + `\t alias ${className.className}, name ${className.className}`, + ) + }) + imports.push(newImport) } - } - else if (childNode.type === 'FileLevelConstant') { - debug(`Adding file level constant ${childNode.name}`); - const [type, attributeType] = parseTypeName(childNode.typeName); + } else if (childNode.type === 'FileLevelConstant') { + debug(`Adding file level constant ${childNode.name}`) + const [type, attributeType] = parseTypeName(childNode.typeName) const umlClass = new umlClass_1.UmlClass({ name: childNode.name, stereotype: umlClass_1.ClassStereotype.Constant, @@ -152,30 +200,29 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals attributeType, }, ], - }); + }) if (childNode?.initialValue?.type === 'NumberLiteral') { umlClass.constants.push({ name: childNode.name, value: parseInt(childNode.initialValue.number), - }); + }) } // TODO handle expressions. eg N_COINS * 2 - umlClasses.push(umlClass); - } - else if (childNode.type !== 'PragmaDirective') { - debug(`node type "${childNode.type}" not parsed in ${relativePath}`); + umlClasses.push(umlClass) + } else if (childNode.type !== 'PragmaDirective') { + debug( + `node type "${childNode.type}" not parsed in ${relativePath}`, + ) } - }); - } - else { - throw new Error(`AST node not of type SourceUnit`); + }) + } else { + throw new Error('AST node not of type SourceUnit') } if (umlClasses.length > 0) { umlClasses.forEach((umlClass) => { - umlClass.imports = imports; - }); - } - else { + umlClass.imports = imports + }) + } else { const importUmlClass = new umlClass_1.UmlClass({ name: 'Import', stereotype: umlClass_1.ClassStereotype.Import, @@ -183,13 +230,13 @@ function convertAST2UmlClasses(node, relativePath, remappings, filesystem = fals ? path.resolve(relativePath) // resolve the absolute path : relativePath, relativePath, - }); - importUmlClass.imports = imports; - umlClasses = [importUmlClass]; + }) + importUmlClass.imports = imports + umlClasses = [importUmlClass] } - return umlClasses; + return umlClasses } -exports.convertAST2UmlClasses = convertAST2UmlClasses; +exports.convertAST2UmlClasses = convertAST2UmlClasses /** * Parse struct definition for UML attributes and associations. * @param node defined in ASTNode as `StructDefinition` @@ -197,15 +244,15 @@ exports.convertAST2UmlClasses = convertAST2UmlClasses; */ function parseStructDefinition(node, umlClass) { node.members.forEach((member) => { - const [type, attributeType] = parseTypeName(member.typeName); + const [type, attributeType] = parseTypeName(member.typeName) umlClass.attributes.push({ name: member.name, type, attributeType, - }); - }); + }) + }) // Recursively parse struct members for associations - addAssociations(node.members, umlClass); + addAssociations(node.members, umlClass) } /** * Parse enum definition for UML attributes and associations. @@ -213,15 +260,15 @@ function parseStructDefinition(node, umlClass) { * @param umlClass that has enum attributes and associations added. This parameter is mutated. */ function parseEnumDefinition(node, umlClass) { - let index = 0; + let index = 0 node.members.forEach((member) => { umlClass.attributes.push({ name: member.name, type: (index++).toString(), - }); - }); + }) + }) // Recursively parse struct members for associations - addAssociations(node.members, umlClass); + addAssociations(node.members, umlClass) } /** * Parse contract definition for UML attributes, operations and associations. @@ -229,7 +276,7 @@ function parseEnumDefinition(node, umlClass) { * @param umlClass that has attributes, operations and associations added. This parameter is mutated. */ function parseContractDefinition(node, umlClass) { - umlClass.stereotype = parseContractKind(node.kind); + umlClass.stereotype = parseContractKind(node.kind) // For each base contract node.baseContracts.forEach((baseClass) => { // Add a realization association @@ -237,49 +284,48 @@ function parseContractDefinition(node, umlClass) { referenceType: umlClass_1.ReferenceType.Storage, targetUmlClassName: baseClass.baseName.namePath, realization: true, - }); - }); + }) + }) // For each sub node node.subNodes.forEach((subNode) => { if ((0, typeGuards_1.isStateVariableDeclaration)(subNode)) { subNode.variables.forEach((variable) => { - const [type, attributeType] = parseTypeName(variable.typeName); - const valueStore = variable.isDeclaredConst || variable.isImmutable; + const [type, attributeType] = parseTypeName(variable.typeName) + const valueStore = + variable.isDeclaredConst || variable.isImmutable umlClass.attributes.push({ visibility: parseVisibility(variable.visibility), name: variable.name, type, attributeType, compiled: valueStore, - }); + }) // Is the variable a constant that could be used in declaring fixed sized arrays if (variable.isDeclaredConst) { if (variable?.expression?.type === 'NumberLiteral') { umlClass.constants.push({ name: variable.name, value: parseInt(variable.expression.number), - }); + }) } // TODO handle expressions. eg N_COINS * 2 } - }); + }) // Recursively parse variables for associations - addAssociations(subNode.variables, umlClass); - } - else if ((0, typeGuards_1.isUsingForDeclaration)(subNode)) { + addAssociations(subNode.variables, umlClass) + } else if ((0, typeGuards_1.isUsingForDeclaration)(subNode)) { // Add association to library contract umlClass.addAssociation({ referenceType: umlClass_1.ReferenceType.Memory, targetUmlClassName: subNode.libraryName, - }); - } - else if ((0, typeGuards_1.isFunctionDefinition)(subNode)) { + }) + } else if ((0, typeGuards_1.isFunctionDefinition)(subNode)) { if (subNode.isConstructor) { umlClass.operators.push({ name: 'constructor', stereotype: umlClass_1.OperatorStereotype.None, parameters: parseParameters(subNode.parameters), - }); + }) } // If a fallback function else if (subNode.name === '') { @@ -288,15 +334,13 @@ function parseContractDefinition(node, umlClass) { stereotype: umlClass_1.OperatorStereotype.Fallback, parameters: parseParameters(subNode.parameters), stateMutability: subNode.stateMutability, - }); - } - else { - let stereotype = umlClass_1.OperatorStereotype.None; + }) + } else { + let stereotype = umlClass_1.OperatorStereotype.None if (subNode.body === null) { - stereotype = umlClass_1.OperatorStereotype.Abstract; - } - else if (subNode.stateMutability === 'payable') { - stereotype = umlClass_1.OperatorStereotype.Payable; + stereotype = umlClass_1.OperatorStereotype.Abstract + } else if (subNode.stateMutability === 'payable') { + stereotype = umlClass_1.OperatorStereotype.Payable } umlClass.operators.push({ visibility: parseVisibility(subNode.visibility), @@ -305,72 +349,69 @@ function parseContractDefinition(node, umlClass) { parameters: parseParameters(subNode.parameters), returnParameters: parseParameters(subNode.returnParameters), modifiers: subNode.modifiers.map((m) => m.name), - }); + }) } // Recursively parse function parameters for associations - addAssociations(subNode.parameters, umlClass); + addAssociations(subNode.parameters, umlClass) if (subNode.returnParameters) { - addAssociations(subNode.returnParameters, umlClass); + addAssociations(subNode.returnParameters, umlClass) } // If no body to the function, it must be either an Interface or Abstract if (subNode.body === null) { - if (umlClass.stereotype !== umlClass_1.ClassStereotype.Interface) { + if ( + umlClass.stereotype !== umlClass_1.ClassStereotype.Interface + ) { // If not Interface, it must be Abstract - umlClass.stereotype = umlClass_1.ClassStereotype.Abstract; + umlClass.stereotype = umlClass_1.ClassStereotype.Abstract } - } - else { + } else { // Recursively parse function statements for associations - addAssociations(subNode.body.statements, umlClass); + addAssociations(subNode.body.statements, umlClass) } - } - else if ((0, typeGuards_1.isModifierDefinition)(subNode)) { + } else if ((0, typeGuards_1.isModifierDefinition)(subNode)) { umlClass.operators.push({ stereotype: umlClass_1.OperatorStereotype.Modifier, name: subNode.name, parameters: parseParameters(subNode.parameters), - }); + }) if (subNode.body && subNode.body.statements) { // Recursively parse modifier statements for associations - addAssociations(subNode.body.statements, umlClass); + addAssociations(subNode.body.statements, umlClass) } - } - else if ((0, typeGuards_1.isEventDefinition)(subNode)) { + } else if ((0, typeGuards_1.isEventDefinition)(subNode)) { umlClass.operators.push({ stereotype: umlClass_1.OperatorStereotype.Event, name: subNode.name, parameters: parseParameters(subNode.parameters), - }); + }) // Recursively parse event parameters for associations - addAssociations(subNode.parameters, umlClass); - } - else if ((0, typeGuards_1.isStructDefinition)(subNode)) { + addAssociations(subNode.parameters, umlClass) + } else if ((0, typeGuards_1.isStructDefinition)(subNode)) { const structClass = new umlClass_1.UmlClass({ name: subNode.name, absolutePath: umlClass.absolutePath, relativePath: umlClass.relativePath, parentId: umlClass.id, stereotype: umlClass_1.ClassStereotype.Struct, - }); - parseStructDefinition(subNode, structClass); - umlClasses.push(structClass); + }) + parseStructDefinition(subNode, structClass) + umlClasses.push(structClass) // list as contract level struct - umlClass.structs.push(structClass.id); - } - else if ((0, typeGuards_1.isEnumDefinition)(subNode)) { + umlClass.structs.push(structClass.id) + } else if ((0, typeGuards_1.isEnumDefinition)(subNode)) { const enumClass = new umlClass_1.UmlClass({ name: subNode.name, absolutePath: umlClass.absolutePath, relativePath: umlClass.relativePath, parentId: umlClass.id, stereotype: umlClass_1.ClassStereotype.Enum, - }); - parseEnumDefinition(subNode, enumClass); - umlClasses.push(enumClass); + }) + parseEnumDefinition(subNode, enumClass) + umlClasses.push(enumClass) // list as contract level enum - umlClass.enums.push(enumClass.id); + umlClass.enums.push(enumClass.id) } - }); + }) } /** * Recursively parse a list of ASTNodes for UML associations @@ -379,133 +420,143 @@ function parseContractDefinition(node, umlClass) { */ function addAssociations(nodes, umlClass) { if (!nodes || !Array.isArray(nodes)) { - debug('Warning - can not recursively parse AST nodes for associations. Invalid nodes array'); - return; + debug( + 'Warning - can not recursively parse AST nodes for associations. Invalid nodes array', + ) + return } for (const node of nodes) { // Some variables can be null. eg var (lad,,,) = tub.cups(cup); if (node === null) { - break; + break } // If state variable then mark as a Storage reference, else Memory const referenceType = node.isStateVar ? umlClass_1.ReferenceType.Storage - : umlClass_1.ReferenceType.Memory; + : umlClass_1.ReferenceType.Memory // Recursively parse sub nodes that can has variable declarations switch (node.type) { case 'VariableDeclaration': if (!node.typeName) { - break; + break } if (node.typeName.type === 'UserDefinedTypeName') { // Library references can have a Library dot variable notation. eg Set.Data // Structs and enums can also be under a library or contract - const { umlClassName, structOrEnum } = parseClassName(node.typeName.namePath); + const { umlClassName, structOrEnum } = parseClassName( + node.typeName.namePath, + ) umlClass.addAssociation({ referenceType, targetUmlClassName: umlClassName, - }); + }) if (structOrEnum) { umlClass.addAssociation({ referenceType, parentUmlClassName: umlClassName, targetUmlClassName: structOrEnum, - }); + }) } - } - else if (node.typeName.type === 'Mapping') { - addAssociations([node.typeName.keyType], umlClass); - addAssociations([ - { - ...node.typeName.valueType, - isStateVar: node.isStateVar, - }, - ], umlClass); + } else if (node.typeName.type === 'Mapping') { + addAssociations([node.typeName.keyType], umlClass) + addAssociations( + [ + { + ...node.typeName.valueType, + isStateVar: node.isStateVar, + }, + ], + umlClass, + ) // Array of user defined types - } - else if (node.typeName.type == 'ArrayTypeName') { - if (node.typeName.baseTypeName.type === - 'UserDefinedTypeName') { - const { umlClassName } = parseClassName(node.typeName.baseTypeName.namePath); + } else if (node.typeName.type == 'ArrayTypeName') { + if ( + node.typeName.baseTypeName.type === + 'UserDefinedTypeName' + ) { + const { umlClassName } = parseClassName( + node.typeName.baseTypeName.namePath, + ) umlClass.addAssociation({ referenceType, targetUmlClassName: umlClassName, - }); - } - else if (node.typeName.length?.type === 'Identifier') { - const { umlClassName } = parseClassName(node.typeName.length.name); + }) + } else if (node.typeName.length?.type === 'Identifier') { + const { umlClassName } = parseClassName( + node.typeName.length.name, + ) umlClass.addAssociation({ referenceType, targetUmlClassName: umlClassName, - }); + }) } } - break; + break case 'UserDefinedTypeName': umlClass.addAssociation({ - referenceType: referenceType, + referenceType, targetUmlClassName: node.namePath, - }); - break; + }) + break case 'Block': - addAssociations(node.statements, umlClass); - break; + addAssociations(node.statements, umlClass) + break case 'StateVariableDeclaration': case 'VariableDeclarationStatement': - addAssociations(node.variables, umlClass); - parseExpression(node.initialValue, umlClass); - break; + addAssociations(node.variables, umlClass) + parseExpression(node.initialValue, umlClass) + break case 'EmitStatement': - addAssociations(node.eventCall.arguments, umlClass); - parseExpression(node.eventCall.expression, umlClass); - break; + addAssociations(node.eventCall.arguments, umlClass) + parseExpression(node.eventCall.expression, umlClass) + break case 'FunctionCall': - addAssociations(node.arguments, umlClass); - parseExpression(node.expression, umlClass); - break; + addAssociations(node.arguments, umlClass) + parseExpression(node.expression, umlClass) + break case 'ForStatement': if ('statements' in node.body) { - addAssociations(node.body.statements, umlClass); + addAssociations(node.body.statements, umlClass) } - parseExpression(node.conditionExpression, umlClass); - parseExpression(node.loopExpression.expression, umlClass); - break; + parseExpression(node.conditionExpression, umlClass) + parseExpression(node.loopExpression.expression, umlClass) + break case 'WhileStatement': if ('statements' in node.body) { - addAssociations(node.body.statements, umlClass); + addAssociations(node.body.statements, umlClass) } - break; + break case 'DoWhileStatement': if ('statements' in node.body) { - addAssociations(node.body.statements, umlClass); + addAssociations(node.body.statements, umlClass) } - parseExpression(node.condition, umlClass); - break; + parseExpression(node.condition, umlClass) + break case 'ReturnStatement': case 'ExpressionStatement': - parseExpression(node.expression, umlClass); - break; + parseExpression(node.expression, umlClass) + break case 'IfStatement': if (node.trueBody) { if ('statements' in node.trueBody) { - addAssociations(node.trueBody.statements, umlClass); + addAssociations(node.trueBody.statements, umlClass) } if ('expression' in node.trueBody) { - parseExpression(node.trueBody.expression, umlClass); + parseExpression(node.trueBody.expression, umlClass) } } if (node.falseBody) { if ('statements' in node.falseBody) { - addAssociations(node.falseBody.statements, umlClass); + addAssociations(node.falseBody.statements, umlClass) } if ('expression' in node.falseBody) { - parseExpression(node.falseBody.expression, umlClass); + parseExpression(node.falseBody.expression, umlClass) } } - parseExpression(node.condition, umlClass); - break; + parseExpression(node.condition, umlClass) + break default: - break; + break } } } @@ -516,46 +567,40 @@ function addAssociations(nodes, umlClass) { */ function parseExpression(expression, umlClass) { if (!expression || !expression.type) { - return; + return } if (expression.type === 'BinaryOperation') { - parseExpression(expression.left, umlClass); - parseExpression(expression.right, umlClass); - } - else if (expression.type === 'FunctionCall') { - parseExpression(expression.expression, umlClass); + parseExpression(expression.left, umlClass) + parseExpression(expression.right, umlClass) + } else if (expression.type === 'FunctionCall') { + parseExpression(expression.expression, umlClass) expression.arguments.forEach((arg) => { - parseExpression(arg, umlClass); - }); - } - else if (expression.type === 'IndexAccess') { - parseExpression(expression.base, umlClass); - parseExpression(expression.index, umlClass); - } - else if (expression.type === 'TupleExpression') { + parseExpression(arg, umlClass) + }) + } else if (expression.type === 'IndexAccess') { + parseExpression(expression.base, umlClass) + parseExpression(expression.index, umlClass) + } else if (expression.type === 'TupleExpression') { expression.components.forEach((component) => { - parseExpression(component, umlClass); - }); - } - else if (expression.type === 'MemberAccess') { - parseExpression(expression.expression, umlClass); - } - else if (expression.type === 'Conditional') { - addAssociations([expression.trueExpression], umlClass); - addAssociations([expression.falseExpression], umlClass); - } - else if (expression.type === 'Identifier') { + parseExpression(component, umlClass) + }) + } else if (expression.type === 'MemberAccess') { + parseExpression(expression.expression, umlClass) + } else if (expression.type === 'Conditional') { + addAssociations([expression.trueExpression], umlClass) + addAssociations([expression.falseExpression], umlClass) + } else if (expression.type === 'Identifier') { umlClass.addAssociation({ referenceType: umlClass_1.ReferenceType.Memory, targetUmlClassName: expression.name, - }); - } - else if (expression.type === 'NewExpression') { - addAssociations([expression.typeName], umlClass); - } - else if (expression.type === 'UnaryOperation' && - expression.subExpression) { - parseExpression(expression.subExpression, umlClass); + }) + } else if (expression.type === 'NewExpression') { + addAssociations([expression.typeName], umlClass) + } else if ( + expression.type === 'UnaryOperation' && + expression.subExpression + ) { + parseExpression(expression.subExpression, umlClass) } } /** @@ -565,20 +610,22 @@ function parseExpression(expression, umlClass) { * @return object with `umlClassName` and `structOrEnum` of type string */ function parseClassName(rawClassName) { - if (!rawClassName || + if ( + !rawClassName || typeof rawClassName !== 'string' || - rawClassName.length === 0) { + rawClassName.length === 0 + ) { return { umlClassName: '', structOrEnum: rawClassName, - }; + } } // Split the name on dot - const splitUmlClassName = rawClassName.split('.'); + const splitUmlClassName = rawClassName.split('.') return { umlClassName: splitUmlClassName[0], structOrEnum: splitUmlClassName[1], - }; + } } /** * Converts the contract visibility to attribute or operator visibility of type `Visibility` @@ -588,17 +635,19 @@ function parseClassName(rawClassName) { function parseVisibility(visibility) { switch (visibility) { case 'default': - return umlClass_1.Visibility.Public; + return umlClass_1.Visibility.Public case 'public': - return umlClass_1.Visibility.Public; + return umlClass_1.Visibility.Public case 'external': - return umlClass_1.Visibility.External; + return umlClass_1.Visibility.External case 'internal': - return umlClass_1.Visibility.Internal; + return umlClass_1.Visibility.Internal case 'private': - return umlClass_1.Visibility.Private; + return umlClass_1.Visibility.Private default: - throw Error(`Invalid visibility ${visibility}. Was not public, external, internal or private`); + throw Error( + `Invalid visibility ${visibility}. Was not public, external, internal or private`, + ) } } /** @@ -610,36 +659,36 @@ function parseVisibility(visibility) { function parseTypeName(typeName) { switch (typeName.type) { case 'ElementaryTypeName': - return [typeName.name, umlClass_1.AttributeType.Elementary]; + return [typeName.name, umlClass_1.AttributeType.Elementary] case 'UserDefinedTypeName': - return [typeName.namePath, umlClass_1.AttributeType.UserDefined]; + return [typeName.namePath, umlClass_1.AttributeType.UserDefined] case 'FunctionTypeName': // TODO add params and return type - return [typeName.type + '\\(\\)', umlClass_1.AttributeType.Function]; + return [typeName.type + '\\(\\)', umlClass_1.AttributeType.Function] case 'ArrayTypeName': - const [arrayElementType] = parseTypeName(typeName.baseTypeName); - let length = ''; + const [arrayElementType] = parseTypeName(typeName.baseTypeName) + let length = '' if (Number.isInteger(typeName.length)) { - length = typeName.length.toString(); - } - else if (typeName.length?.type === 'NumberLiteral') { - length = typeName.length.number; - } - else if (typeName.length?.type === 'Identifier') { - length = typeName.length.name; + length = typeName.length.toString() + } else if (typeName.length?.type === 'NumberLiteral') { + length = typeName.length.number + } else if (typeName.length?.type === 'Identifier') { + length = typeName.length.name } // TODO does not currently handle Expression types like BinaryOperation - return [arrayElementType + '[' + length + ']', umlClass_1.AttributeType.Array]; + return [ + arrayElementType + '[' + length + ']', + umlClass_1.AttributeType.Array, + ] case 'Mapping': - const key = typeName.keyType?.name || - typeName.keyType?.namePath; - const [valueType] = parseTypeName(typeName.valueType); + const key = typeName.keyType?.name || typeName.keyType?.namePath + const [valueType] = parseTypeName(typeName.valueType) return [ 'mapping\\(' + key + '=\\>' + valueType + '\\)', umlClass_1.AttributeType.Mapping, - ]; + ] default: - throw Error(`Invalid typeName ${typeName}`); + throw Error(`Invalid typeName ${typeName}`) } } /** @@ -649,17 +698,17 @@ function parseTypeName(typeName) { */ function parseParameters(params) { if (!params || !params) { - return []; + return [] } - let parameters = []; + const parameters = [] for (const param of params) { - const [type] = parseTypeName(param.typeName); + const [type] = parseTypeName(param.typeName) parameters.push({ name: param.name, type, - }); + }) } - return parameters; + return parameters } /** * Converts the contract `kind` to `UMLClass` stereotype @@ -669,15 +718,15 @@ function parseParameters(params) { function parseContractKind(kind) { switch (kind) { case 'contract': - return umlClass_1.ClassStereotype.Contract; + return umlClass_1.ClassStereotype.Contract case 'interface': - return umlClass_1.ClassStereotype.Interface; + return umlClass_1.ClassStereotype.Interface case 'library': - return umlClass_1.ClassStereotype.Library; + return umlClass_1.ClassStereotype.Library case 'abstract': - return umlClass_1.ClassStereotype.Abstract; + return umlClass_1.ClassStereotype.Abstract default: - throw Error(`Invalid kind ${kind}`); + throw Error(`Invalid kind ${kind}`) } } /** @@ -689,16 +738,16 @@ function parseContractKind(kind) { * @param mappings an array of remappings from Etherscan's settings */ const renameFile = (fileName, mappings) => { - let renamedFile = fileName; + let renamedFile = fileName for (const mapping of mappings) { if (renamedFile.match(mapping.from)) { - const beforeFileName = renamedFile; - renamedFile = renamedFile.replace(mapping.from, mapping.to); - debug(`remapping ${beforeFileName} to ${renamedFile}`); - break; + const beforeFileName = renamedFile + renamedFile = renamedFile.replace(mapping.from, mapping.to) + debug(`remapping ${beforeFileName} to ${renamedFile}`) + break } } - return renamedFile; -}; -exports.renameFile = renameFile; -//# sourceMappingURL=converterAST2Classes.js.map \ No newline at end of file + return renamedFile +} +exports.renameFile = renameFile +// # sourceMappingURL=converterAST2Classes.js.map diff --git a/lib/converterClass2Dot.d.ts b/lib/converterClass2Dot.d.ts index 2518c1a8..bf987571 100644 --- a/lib/converterClass2Dot.d.ts +++ b/lib/converterClass2Dot.d.ts @@ -1,22 +1,25 @@ -import { UmlClass } from './umlClass'; +import { UmlClass } from './umlClass' export interface ClassOptions { - hideConstants?: boolean; - hideContracts?: boolean; - hideVariables?: boolean; - hideFunctions?: boolean; - hideModifiers?: boolean; - hideEvents?: boolean; - hideStructs?: boolean; - hideEnums?: boolean; - hideLibraries?: boolean; - hideInterfaces?: boolean; - hidePrivates?: boolean; - hideAbstracts?: boolean; - hideFilename?: boolean; - hideSourceContract?: boolean; - backColor?: string; - shapeColor?: string; - fillColor?: string; - textColor?: string; + hideConstants?: boolean + hideContracts?: boolean + hideVariables?: boolean + hideFunctions?: boolean + hideModifiers?: boolean + hideEvents?: boolean + hideStructs?: boolean + hideEnums?: boolean + hideLibraries?: boolean + hideInterfaces?: boolean + hidePrivates?: boolean + hideAbstracts?: boolean + hideFilename?: boolean + hideSourceContract?: boolean + backColor?: string + shapeColor?: string + fillColor?: string + textColor?: string } -export declare const convertClass2Dot: (umlClass: UmlClass, options?: ClassOptions) => string; +export declare const convertClass2Dot: ( + umlClass: UmlClass, + options?: ClassOptions, +) => string diff --git a/lib/converterClass2Dot.js b/lib/converterClass2Dot.js index dc2c1cef..c6000748 100644 --- a/lib/converterClass2Dot.js +++ b/lib/converterClass2Dot.js @@ -1,12 +1,13 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.convertClass2Dot = void 0; +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.convertClass2Dot = void 0 // Returns a string of the UML Class in Graphviz's dot format -const umlClass_1 = require("./umlClass"); -const regEx_1 = require("./utils/regEx"); +const umlClass_1 = require('./umlClass') +const regEx_1 = require('./utils/regEx') const convertClass2Dot = (umlClass, options = {}) => { // do not include library, interface, abstracts, struct or enum classes if hidden - if (umlClass.stereotype === umlClass_1.ClassStereotype.Import || + if ( + umlClass.stereotype === umlClass_1.ClassStereotype.Import || (options.hideLibraries && umlClass.stereotype === umlClass_1.ClassStereotype.Library) || (options.hideInterfaces && @@ -15,229 +16,255 @@ const convertClass2Dot = (umlClass, options = {}) => { umlClass.stereotype === umlClass_1.ClassStereotype.Abstract) || (options.hideStructs && umlClass.stereotype === umlClass_1.ClassStereotype.Struct) || - (options.hideEnums && umlClass.stereotype === umlClass_1.ClassStereotype.Enum) || + (options.hideEnums && + umlClass.stereotype === umlClass_1.ClassStereotype.Enum) || (options.hideConstants && - umlClass.stereotype === umlClass_1.ClassStereotype.Constant)) { - return ''; + umlClass.stereotype === umlClass_1.ClassStereotype.Constant) + ) { + return '' } - let dotString = `\n${umlClass.id} [label="{${dotClassTitle(umlClass, options)}`; + let dotString = `\n${umlClass.id} [label="{${dotClassTitle(umlClass, options)}` // Add attributes if (!options.hideVariables) { - dotString += dotAttributeVisibilities(umlClass, options); + dotString += dotAttributeVisibilities(umlClass, options) } // Add operators if (!options.hideFunctions) { - dotString += dotOperatorVisibilities(umlClass, options); + dotString += dotOperatorVisibilities(umlClass, options) } - dotString += '}"]'; - return dotString; -}; -exports.convertClass2Dot = convertClass2Dot; + dotString += '}"]' + return dotString +} +exports.convertClass2Dot = convertClass2Dot const dotClassTitle = (umlClass, options = {}) => { - let stereoName = ''; - const relativePath = options.hideFilename || (0, regEx_1.isAddress)(umlClass.relativePath) - ? '' - : `\\n${umlClass.relativePath}`; + let stereoName = '' + const relativePath = + options.hideFilename || (0, regEx_1.isAddress)(umlClass.relativePath) + ? '' + : `\\n${umlClass.relativePath}` switch (umlClass.stereotype) { case umlClass_1.ClassStereotype.Abstract: - stereoName = 'Abstract'; - break; + stereoName = 'Abstract' + break case umlClass_1.ClassStereotype.Interface: - stereoName = 'Interface'; - break; + stereoName = 'Interface' + break case umlClass_1.ClassStereotype.Library: - stereoName = 'Library'; - break; + stereoName = 'Library' + break case umlClass_1.ClassStereotype.Struct: - stereoName = 'Struct'; - break; + stereoName = 'Struct' + break case umlClass_1.ClassStereotype.Enum: - stereoName = 'Enum'; - break; + stereoName = 'Enum' + break case umlClass_1.ClassStereotype.Constant: - stereoName = 'Constant'; - break; + stereoName = 'Constant' + break default: // Contract or undefined stereotype will just return the UmlClass name - return `${umlClass.name}${relativePath}`; + return `${umlClass.name}${relativePath}` } - return `\\<\\<${stereoName}\\>\\>\\n${umlClass.name}${relativePath}`; -}; + return `\\<\\<${stereoName}\\>\\>\\n${umlClass.name}${relativePath}` +} const dotAttributeVisibilities = (umlClass, options) => { - if (umlClass.attributes.length === 0) - return ''; - let dotString = '| '; + if (umlClass.attributes.length === 0) { + return '' + } + let dotString = '| ' // if a struct, enum or constant then no visibility group - if (umlClass.stereotype === umlClass_1.ClassStereotype.Struct || + if ( + umlClass.stereotype === umlClass_1.ClassStereotype.Struct || umlClass.stereotype === umlClass_1.ClassStereotype.Enum || - umlClass.stereotype === umlClass_1.ClassStereotype.Constant) { - return (dotString + - dotAttributes(umlClass.attributes, options, undefined, false)); + umlClass.stereotype === umlClass_1.ClassStereotype.Constant + ) { + return ( + dotString + + dotAttributes(umlClass.attributes, options, undefined, false) + ) } // For each visibility group for (const vizGroup of ['Private', 'Internal', 'External', 'Public']) { - const attributes = []; + const attributes = [] // For each attribute of te UML Class for (const attribute of umlClass.attributes) { - if (!options.hidePrivates && + if ( + !options.hidePrivates && vizGroup === 'Private' && - attribute.visibility === umlClass_1.Visibility.Private) { - attributes.push(attribute); - } - else if (!options.hidePrivates && + attribute.visibility === umlClass_1.Visibility.Private + ) { + attributes.push(attribute) + } else if ( + !options.hidePrivates && vizGroup === 'Internal' && - attribute.visibility === umlClass_1.Visibility.Internal) { - attributes.push(attribute); - } - else if (vizGroup === 'External' && - attribute.visibility === umlClass_1.Visibility.External) { - attributes.push(attribute); + attribute.visibility === umlClass_1.Visibility.Internal + ) { + attributes.push(attribute) + } else if ( + vizGroup === 'External' && + attribute.visibility === umlClass_1.Visibility.External + ) { + attributes.push(attribute) } // Rest are Public, None or undefined visibilities - else if (vizGroup === 'Public' && + else if ( + vizGroup === 'Public' && (attribute.visibility === umlClass_1.Visibility.Public || attribute.visibility === umlClass_1.Visibility.None || - !attribute.visibility)) { - attributes.push(attribute); + !attribute.visibility) + ) { + attributes.push(attribute) } } - dotString += dotAttributes(attributes, options, vizGroup); + dotString += dotAttributes(attributes, options, vizGroup) } - return dotString; -}; + return dotString +} const dotAttributes = (attributes, options, vizGroup, indent = true) => { if (!attributes || attributes.length === 0) { - return ''; + return '' } - const indentString = indent ? '\\ \\ \\ ' : ''; - let dotString = vizGroup ? vizGroup + ':\\l' : ''; + const indentString = indent ? '\\ \\ \\ ' : '' + let dotString = vizGroup ? vizGroup + ':\\l' : '' // for each attribute attributes.forEach((attribute) => { - const sourceContract = attribute.sourceContract && !options.hideSourceContract - ? ` \\<\\<${attribute.sourceContract}\\>\\>` - : ''; - dotString += `${indentString}${attribute.name}: ${attribute.type}${sourceContract}\\l`; - }); - return dotString; -}; + const sourceContract = + attribute.sourceContract && !options.hideSourceContract + ? ` \\<\\<${attribute.sourceContract}\\>\\>` + : '' + dotString += `${indentString}${attribute.name}: ${attribute.type}${sourceContract}\\l` + }) + return dotString +} const dotOperatorVisibilities = (umlClass, options) => { - if (umlClass.operators.length === 0) - return ''; - let dotString = '| '; + if (umlClass.operators.length === 0) { + return '' + } + let dotString = '| ' // For each visibility group for (const vizGroup of ['Private', 'Internal', 'External', 'Public']) { - const operators = []; + const operators = [] // For each attribute of te UML Class for (const operator of umlClass.operators) { - if (!options.hidePrivates && + if ( + !options.hidePrivates && vizGroup === 'Private' && - operator.visibility === umlClass_1.Visibility.Private) { - operators.push(operator); - } - else if (!options.hidePrivates && + operator.visibility === umlClass_1.Visibility.Private + ) { + operators.push(operator) + } else if ( + !options.hidePrivates && vizGroup === 'Internal' && - operator.visibility === umlClass_1.Visibility.Internal) { - operators.push(operator); - } - else if (vizGroup === 'External' && - operator.visibility === umlClass_1.Visibility.External) { - operators.push(operator); + operator.visibility === umlClass_1.Visibility.Internal + ) { + operators.push(operator) + } else if ( + vizGroup === 'External' && + operator.visibility === umlClass_1.Visibility.External + ) { + operators.push(operator) } // Rest are Public, None or undefined visibilities - else if (vizGroup === 'Public' && + else if ( + vizGroup === 'Public' && (operator.visibility === umlClass_1.Visibility.Public || operator.visibility === umlClass_1.Visibility.None || - !operator.visibility)) { - operators.push(operator); + !operator.visibility) + ) { + operators.push(operator) } } - dotString += dotOperators(umlClass, vizGroup, operators, options); + dotString += dotOperators(umlClass, vizGroup, operators, options) } - return dotString; -}; + return dotString +} const dotOperators = (umlClass, vizGroup, operators, options) => { // Skip if there are no operators if (!operators || operators.length === 0) { - return ''; + return '' } - let dotString = vizGroup + ':\\l'; + let dotString = vizGroup + ':\\l' // Sort the operators by stereotypes const operatorsSortedByStereotype = operators.sort((a, b) => { - return b.stereotype - a.stereotype; - }); + return b.stereotype - a.stereotype + }) // Filter out any modifiers or events if options are flagged to hide them - let operatorsFiltered = operatorsSortedByStereotype.filter((o) => !((options.hideModifiers === true && - o.stereotype === umlClass_1.OperatorStereotype.Modifier) || - (options.hideEvents === true && - o.stereotype === umlClass_1.OperatorStereotype.Event))); + const operatorsFiltered = operatorsSortedByStereotype.filter( + (o) => + !( + (options.hideModifiers === true && + o.stereotype === umlClass_1.OperatorStereotype.Modifier) || + (options.hideEvents === true && + o.stereotype === umlClass_1.OperatorStereotype.Event) + ), + ) for (const operator of operatorsFiltered) { - dotString += '\\ \\ \\ \\ '; + dotString += '\\ \\ \\ \\ ' if (operator.stereotype > 0) { - dotString += dotOperatorStereotype(umlClass, operator.stereotype); + dotString += dotOperatorStereotype(umlClass, operator.stereotype) } - dotString += operator.name; - dotString += dotParameters(operator.parameters); + dotString += operator.name + dotString += dotParameters(operator.parameters) if (operator.returnParameters?.length > 0) { - dotString += ': ' + dotParameters(operator.returnParameters, true); + dotString += ': ' + dotParameters(operator.returnParameters, true) } if (options.hideModifiers === false && operator.modifiers?.length > 0) { - dotString += ` \\<\\<${operator.modifiers.join(', ')}\\>\\>`; + dotString += ` \\<\\<${operator.modifiers.join(', ')}\\>\\>` + } + if (operator.sourceContract && !options.hideSourceContract) { + dotString += ` \\<\\<${operator.sourceContract}\\>\\>` } - if (operator.sourceContract && !options.hideSourceContract) - dotString += ` \\<\\<${operator.sourceContract}\\>\\>`; - dotString += '\\l'; + dotString += '\\l' } - return dotString; -}; + return dotString +} const dotOperatorStereotype = (umlClass, operatorStereotype) => { - let dotString = ''; + let dotString = '' switch (operatorStereotype) { case umlClass_1.OperatorStereotype.Event: - dotString += '\\<\\\\>'; - break; + dotString += '\\<\\\\>' + break case umlClass_1.OperatorStereotype.Fallback: - dotString += '\\<\\\\>'; - break; + dotString += '\\<\\\\>' + break case umlClass_1.OperatorStereotype.Modifier: - dotString += '\\<\\\\>'; - break; + dotString += '\\<\\\\>' + break case umlClass_1.OperatorStereotype.Abstract: if (umlClass.stereotype === umlClass_1.ClassStereotype.Abstract) { - dotString += '\\<\\\\>'; + dotString += '\\<\\\\>' } - break; + break case umlClass_1.OperatorStereotype.Payable: - dotString += '\\<\\\\>'; - break; + dotString += '\\<\\\\>' + break default: - break; + break } - return dotString + ' '; -}; + return dotString + ' ' +} const dotParameters = (parameters, returnParams = false) => { if (parameters.length == 1 && !parameters[0].name) { if (returnParams) { - return parameters[0].type; - } - else { - return `(${parameters[0].type})`; + return parameters[0].type + } else { + return `(${parameters[0].type})` } } - let dotString = '('; - let paramCount = 0; + let dotString = '(' + let paramCount = 0 for (const parameter of parameters) { // The parameter name can be null in return parameters if (parameter.name === null) { - dotString += parameter.type; - } - else { - dotString += parameter.name + ': ' + parameter.type; + dotString += parameter.type + } else { + dotString += parameter.name + ': ' + parameter.type } // If not the last parameter if (++paramCount < parameters.length) { - dotString += ', '; + dotString += ', ' } } - return dotString + ')'; -}; -//# sourceMappingURL=converterClass2Dot.js.map \ No newline at end of file + return dotString + ')' +} +// # sourceMappingURL=converterClass2Dot.js.map diff --git a/lib/converterClasses2Dot.d.ts b/lib/converterClasses2Dot.d.ts index b46f698e..7a60ba31 100644 --- a/lib/converterClasses2Dot.d.ts +++ b/lib/converterClasses2Dot.d.ts @@ -1,5 +1,5 @@ -import { ClassOptions } from './converterClass2Dot'; -import { UmlClass } from './umlClass'; +import { ClassOptions } from './converterClass2Dot' +import { UmlClass } from './umlClass' /** * Converts UML classes to Graphviz's DOT format. * The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html @@ -8,5 +8,12 @@ import { UmlClass } from './umlClass'; * @param classOptions command line options for the `class` command * @return dotString Graphviz's DOT format for defining nodes, edges and clusters. */ -export declare function convertUmlClasses2Dot(umlClasses: UmlClass[], clusterFolders?: boolean, classOptions?: ClassOptions): string; -export declare function addAssociationsToDot(umlClasses: UmlClass[], classOptions?: ClassOptions): string; +export declare function convertUmlClasses2Dot( + umlClasses: UmlClass[], + clusterFolders?: boolean, + classOptions?: ClassOptions, +): string +export declare function addAssociationsToDot( + umlClasses: UmlClass[], + classOptions?: ClassOptions, +): string diff --git a/lib/converterClasses2Dot.js b/lib/converterClasses2Dot.js index e977ecd0..2b60cdee 100644 --- a/lib/converterClasses2Dot.js +++ b/lib/converterClasses2Dot.js @@ -1,11 +1,11 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.addAssociationsToDot = exports.convertUmlClasses2Dot = void 0; -const path_1 = require("path"); -const converterClass2Dot_1 = require("./converterClass2Dot"); -const umlClass_1 = require("./umlClass"); -const associations_1 = require("./associations"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.addAssociationsToDot = exports.convertUmlClasses2Dot = void 0 +const path_1 = require('path') +const converterClass2Dot_1 = require('./converterClass2Dot') +const umlClass_1 = require('./umlClass') +const associations_1 = require('./associations') +const debug = require('debug')('sol2uml') /** * Converts UML classes to Graphviz's DOT format. * The DOT grammar defines Graphviz nodes, edges, graphs, subgraphs, and clusters http://www.graphviz.org/doc/info/lang.html @@ -14,132 +14,161 @@ const debug = require('debug')('sol2uml'); * @param classOptions command line options for the `class` command * @return dotString Graphviz's DOT format for defining nodes, edges and clusters. */ -function convertUmlClasses2Dot(umlClasses, clusterFolders = false, classOptions = {}) { +function convertUmlClasses2Dot( + umlClasses, + clusterFolders = false, + classOptions = {}, +) { let dotString = ` digraph UmlClassDiagram { rankdir=BT arrowhead=open bgcolor="${classOptions.backColor}" edge [color="${classOptions.shapeColor}"] -node [shape=record, style=filled, color="${classOptions.shapeColor}", fillcolor="${classOptions.fillColor}", fontcolor="${classOptions.textColor}"]`; +node [shape=record, style=filled, color="${classOptions.shapeColor}", fillcolor="${classOptions.fillColor}", fontcolor="${classOptions.textColor}"]` // Sort UML Classes by folder of source file - const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses); - let currentCodeFolder = ''; + const umlClassesSortedByCodePath = sortUmlClassesByCodePath(umlClasses) + let currentCodeFolder = '' for (const umlClass of umlClassesSortedByCodePath) { - const codeFolder = (0, path_1.dirname)(umlClass.relativePath); + const codeFolder = (0, path_1.dirname)(umlClass.relativePath) if (currentCodeFolder !== codeFolder) { // Need to close off the last subgraph if not the first if (currentCodeFolder != '') { - dotString += '\n}'; + dotString += '\n}' } dotString += ` subgraph ${getSubGraphName(clusterFolders)} { -label="${codeFolder}"`; - currentCodeFolder = codeFolder; +label="${codeFolder}"` + currentCodeFolder = codeFolder } - dotString += (0, converterClass2Dot_1.convertClass2Dot)(umlClass, classOptions); + dotString += (0, converterClass2Dot_1.convertClass2Dot)( + umlClass, + classOptions, + ) } // Need to close off the last subgraph if not the first if (currentCodeFolder != '') { - dotString += '\n}'; + dotString += '\n}' } - dotString += addAssociationsToDot(umlClasses, classOptions); + dotString += addAssociationsToDot(umlClasses, classOptions) // Need to close off the last the digraph - dotString += '\n}'; - debug(dotString); - return dotString; + dotString += '\n}' + debug(dotString) + return dotString } -exports.convertUmlClasses2Dot = convertUmlClasses2Dot; -let subGraphCount = 0; +exports.convertUmlClasses2Dot = convertUmlClasses2Dot +let subGraphCount = 0 function getSubGraphName(clusterFolders = false) { if (clusterFolders) { - return ` cluster_${subGraphCount++}`; + return ` cluster_${subGraphCount++}` } - return ` graph_${subGraphCount++}`; + return ` graph_${subGraphCount++}` } function sortUmlClassesByCodePath(umlClasses) { return umlClasses.sort((a, b) => { if (a.relativePath < b.relativePath) { - return -1; + return -1 } if (a.relativePath > b.relativePath) { - return 1; + return 1 } - return 0; - }); + return 0 + }) } function addAssociationsToDot(umlClasses, classOptions = {}) { - let dotString = ''; + let dotString = '' // for each class for (const sourceUmlClass of umlClasses) { if (!classOptions.hideEnums) { // for each enum in the class sourceUmlClass.enums.forEach((enumId) => { // Has the enum been filtered out? eg depth limited - const targetUmlClass = umlClasses.find((c) => c.id === enumId); + const targetUmlClass = umlClasses.find((c) => c.id === enumId) if (targetUmlClass) { // Draw aggregated link from contract to contract level Enum - dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`; + dotString += `\n${enumId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]` } - }); + }) } if (!classOptions.hideStructs) { // for each struct in the class sourceUmlClass.structs.forEach((structId) => { // Has the struct been filtered out? eg depth limited - const targetUmlClass = umlClasses.find((c) => c.id === structId); + const targetUmlClass = umlClasses.find((c) => c.id === structId) if (targetUmlClass) { // Draw aggregated link from contract to contract level Struct - dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]`; + dotString += `\n${structId} -> ${sourceUmlClass.id} [arrowhead=diamond, weight=2]` } - }); + }) } // for each association in that class for (const association of Object.values(sourceUmlClass.associations)) { - const targetUmlClass = (0, associations_1.findAssociatedClass)(association, sourceUmlClass, umlClasses); + const targetUmlClass = (0, associations_1.findAssociatedClass)( + association, + sourceUmlClass, + umlClasses, + ) if (targetUmlClass) { - dotString += addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions); + dotString += addAssociationToDot( + sourceUmlClass, + targetUmlClass, + association, + classOptions, + ) } } } - return dotString; + return dotString } -exports.addAssociationsToDot = addAssociationsToDot; -function addAssociationToDot(sourceUmlClass, targetUmlClass, association, classOptions = {}) { +exports.addAssociationsToDot = addAssociationsToDot +function addAssociationToDot( + sourceUmlClass, + targetUmlClass, + association, + classOptions = {}, +) { // do not include library or interface associations if hidden // Or associations to Structs, Enums or Constants if they are hidden - if ((classOptions.hideLibraries && - (sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Library || - targetUmlClass.stereotype === umlClass_1.ClassStereotype.Library)) || + if ( + (classOptions.hideLibraries && + (sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Library || + targetUmlClass.stereotype === + umlClass_1.ClassStereotype.Library)) || (classOptions.hideInterfaces && - (targetUmlClass.stereotype === umlClass_1.ClassStereotype.Interface || - sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Interface)) || + (targetUmlClass.stereotype === + umlClass_1.ClassStereotype.Interface || + sourceUmlClass.stereotype === + umlClass_1.ClassStereotype.Interface)) || (classOptions.hideAbstracts && - (targetUmlClass.stereotype === umlClass_1.ClassStereotype.Abstract || - sourceUmlClass.stereotype === umlClass_1.ClassStereotype.Abstract)) || + (targetUmlClass.stereotype === + umlClass_1.ClassStereotype.Abstract || + sourceUmlClass.stereotype === + umlClass_1.ClassStereotype.Abstract)) || (classOptions.hideStructs && targetUmlClass.stereotype === umlClass_1.ClassStereotype.Struct) || (classOptions.hideEnums && targetUmlClass.stereotype === umlClass_1.ClassStereotype.Enum) || (classOptions.hideConstants && - targetUmlClass.stereotype === umlClass_1.ClassStereotype.Constant)) { - return ''; + targetUmlClass.stereotype === umlClass_1.ClassStereotype.Constant) + ) { + return '' } - let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [`; - if (association.referenceType == umlClass_1.ReferenceType.Memory || + let dotString = `\n${sourceUmlClass.id} -> ${targetUmlClass.id} [` + if ( + association.referenceType == umlClass_1.ReferenceType.Memory || (association.realization && - targetUmlClass.stereotype === umlClass_1.ClassStereotype.Interface)) { - dotString += 'style=dashed, '; + targetUmlClass.stereotype === umlClass_1.ClassStereotype.Interface) + ) { + dotString += 'style=dashed, ' } if (association.realization) { - dotString += 'arrowhead=empty, arrowsize=3, '; + dotString += 'arrowhead=empty, arrowsize=3, ' if (!targetUmlClass.stereotype) { - dotString += 'weight=4, '; - } - else { - dotString += 'weight=3, '; + dotString += 'weight=4, ' + } else { + dotString += 'weight=3, ' } } - return dotString + ']'; + return dotString + ']' } -//# sourceMappingURL=converterClasses2Dot.js.map \ No newline at end of file +// # sourceMappingURL=converterClasses2Dot.js.map diff --git a/lib/converterClasses2Storage.d.ts b/lib/converterClasses2Storage.d.ts index ee5e7d0c..7bd3f1b2 100644 --- a/lib/converterClasses2Storage.d.ts +++ b/lib/converterClasses2Storage.d.ts @@ -1,41 +1,41 @@ -import { Attribute, AttributeType, UmlClass } from './umlClass'; -import { BigNumberish } from '@ethersproject/bignumber'; +import { Attribute, AttributeType, UmlClass } from './umlClass' +import { BigNumberish } from '@ethersproject/bignumber' export declare enum StorageSectionType { - Contract = "Contract", - Struct = "Struct", - Array = "Array", - Bytes = "Bytes", - String = "String" + Contract = 'Contract', + Struct = 'Struct', + Array = 'Array', + Bytes = 'Bytes', + String = 'String', } export interface Variable { - id: number; - fromSlot?: number; - toSlot?: number; - offset?: string; - byteSize: number; - byteOffset: number; - type: string; - attributeType: AttributeType; - dynamic: boolean; - name?: string; - contractName?: string; - displayValue: boolean; - getValue?: boolean; - slotValue?: string; - parsedValue?: string; - referenceSectionId?: number; - enumValues?: string[]; + id: number + fromSlot?: number + toSlot?: number + offset?: string + byteSize: number + byteOffset: number + type: string + attributeType: AttributeType + dynamic: boolean + name?: string + contractName?: string + displayValue: boolean + getValue?: boolean + slotValue?: string + parsedValue?: string + referenceSectionId?: number + enumValues?: string[] } export interface StorageSection { - id: number; - name: string; - address?: string; - offset?: string; - type: StorageSectionType; - arrayLength?: number; - arrayDynamic?: boolean; - mapping: boolean; - variables: Variable[]; + id: number + name: string + address?: string + offset?: string + type: StorageSectionType + arrayLength?: number + arrayDynamic?: boolean + mapping: boolean + variables: Variable[] } /** * @@ -45,11 +45,21 @@ export interface StorageSection { * @param contractFilename relative path of the contract in the file system * @return storageSections array of storageSection objects */ -export declare const convertClasses2StorageSections: (contractName: string, umlClasses: UmlClass[], arrayItems: number, contractFilename?: string, noExpandVariables?: string[]) => StorageSection[]; -export declare const optionStorageVariables: (contractName: string, slotNames?: { - name: string; - offset: string; -}[], slotTypes?: string[]) => Variable[]; +export declare const convertClasses2StorageSections: ( + contractName: string, + umlClasses: UmlClass[], + arrayItems: number, + contractFilename?: string, + noExpandVariables?: string[], +) => StorageSection[] +export declare const optionStorageVariables: ( + contractName: string, + slotNames?: { + name: string + offset: string + }[], + slotTypes?: string[], +) => Variable[] /** * Recursively adds new storage sections under a class attribute. * also returns the allowed enum values @@ -62,17 +72,36 @@ export declare const optionStorageVariables: (contractName: string, slotNames?: * @return storageSection new storage section that was added or undefined if none was added. * @return enumValues array of allowed enum values. undefined if attribute is not an enum */ -export declare const parseStorageSectionFromAttribute: (attribute: Attribute, umlClass: UmlClass, otherClasses: readonly UmlClass[], storageSections: StorageSection[], mapping: boolean, arrayItems: number, noExpandVariables: string[]) => { - storageSection: StorageSection; - enumValues?: string[]; -}; -export declare const calcStorageByteSize: (attribute: Attribute, umlClass: UmlClass, otherClasses: readonly UmlClass[]) => { - size: number; - dynamic: boolean; -}; -export declare const isElementary: (type: string) => boolean; -export declare const calcSectionOffset: (variable: Variable, sectionOffset?: string) => string; -export declare const findDimensionLength: (umlClass: UmlClass, dimension: string, otherClasses: readonly UmlClass[]) => number; +export declare const parseStorageSectionFromAttribute: ( + attribute: Attribute, + umlClass: UmlClass, + otherClasses: readonly UmlClass[], + storageSections: StorageSection[], + mapping: boolean, + arrayItems: number, + noExpandVariables: string[], +) => { + storageSection: StorageSection + enumValues?: string[] +} +export declare const calcStorageByteSize: ( + attribute: Attribute, + umlClass: UmlClass, + otherClasses: readonly UmlClass[], +) => { + size: number + dynamic: boolean +} +export declare const isElementary: (type: string) => boolean +export declare const calcSectionOffset: ( + variable: Variable, + sectionOffset?: string, +) => string +export declare const findDimensionLength: ( + umlClass: UmlClass, + dimension: string, + otherClasses: readonly UmlClass[], +) => number /** * Recursively adds variables for dynamic string, bytes or arrays * @param storageSection @@ -82,4 +111,11 @@ export declare const findDimensionLength: (umlClass: UmlClass, dimension: string * @param arrayItems the number of items to display at the start and end of an array * @param blockTag block number or `latest` */ -export declare const addDynamicVariables: (storageSection: StorageSection, storageSections: StorageSection[], url: string, contractAddress: string, arrayItems: number, blockTag: BigNumberish) => Promise; +export declare const addDynamicVariables: ( + storageSection: StorageSection, + storageSections: StorageSection[], + url: string, + contractAddress: string, + arrayItems: number, + blockTag: BigNumberish, +) => Promise diff --git a/lib/converterClasses2Storage.js b/lib/converterClasses2Storage.js index bdfc184c..3968d312 100644 --- a/lib/converterClasses2Storage.js +++ b/lib/converterClasses2Storage.js @@ -1,26 +1,37 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.addDynamicVariables = exports.findDimensionLength = exports.calcSectionOffset = exports.isElementary = exports.calcStorageByteSize = exports.parseStorageSectionFromAttribute = exports.optionStorageVariables = exports.convertClasses2StorageSections = exports.StorageSectionType = void 0; -const umlClass_1 = require("./umlClass"); -const associations_1 = require("./associations"); -const utils_1 = require("ethers/lib/utils"); -const ethers_1 = require("ethers"); -const path_1 = __importDefault(require("path")); -const slotValues_1 = require("./slotValues"); -const debug = require('debug')('sol2uml'); -var StorageSectionType; -(function (StorageSectionType) { - StorageSectionType["Contract"] = "Contract"; - StorageSectionType["Struct"] = "Struct"; - StorageSectionType["Array"] = "Array"; - StorageSectionType["Bytes"] = "Bytes"; - StorageSectionType["String"] = "String"; -})(StorageSectionType || (exports.StorageSectionType = StorageSectionType = {})); -let storageId = 1; -let variableId = 1; +'use strict' +const __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod } + } +Object.defineProperty(exports, '__esModule', { value: true }) +exports.addDynamicVariables = + exports.findDimensionLength = + exports.calcSectionOffset = + exports.isElementary = + exports.calcStorageByteSize = + exports.parseStorageSectionFromAttribute = + exports.optionStorageVariables = + exports.convertClasses2StorageSections = + exports.StorageSectionType = + void 0 +const umlClass_1 = require('./umlClass') +const associations_1 = require('./associations') +const utils_1 = require('ethers/lib/utils') +const ethers_1 = require('ethers') +const path_1 = __importDefault(require('path')) +const slotValues_1 = require('./slotValues') +const debug = require('debug')('sol2uml') +let StorageSectionType +;(function (StorageSectionType) { + StorageSectionType.Contract = 'Contract' + StorageSectionType.Struct = 'Struct' + StorageSectionType.Array = 'Array' + StorageSectionType.Bytes = 'Bytes' + StorageSectionType.String = 'String' +})(StorageSectionType || (exports.StorageSectionType = StorageSectionType = {})) +let storageId = 1 +let variableId = 1 /** * * @param contractName name of the contract to get storage layout. @@ -29,54 +40,75 @@ let variableId = 1; * @param contractFilename relative path of the contract in the file system * @return storageSections array of storageSection objects */ -const convertClasses2StorageSections = (contractName, umlClasses, arrayItems, contractFilename, noExpandVariables = []) => { +const convertClasses2StorageSections = ( + contractName, + umlClasses, + arrayItems, + contractFilename, + noExpandVariables = [], +) => { // Find the base UML Class from the base contract name const umlClass = umlClasses.find(({ name, relativePath }) => { if (!contractFilename) { - return name === contractName; + return name === contractName } - return (name === contractName && + return ( + name === contractName && (relativePath == path_1.default.normalize(contractFilename) || path_1.default.basename(relativePath) === - path_1.default.normalize(contractFilename))); - }); + path_1.default.normalize(contractFilename)) + ) + }) if (!umlClass) { const contractFilenameError = contractFilename ? ` in filename "${contractFilename}"` - : ''; - throw Error(`Failed to find contract with name "${contractName}"${contractFilenameError}.\nIs the \`-c --contract \` option correct?`); + : '' + throw Error( + `Failed to find contract with name "${contractName}"${contractFilenameError}.\nIs the \`-c --contract \` option correct?`, + ) } - debug(`Found contract "${contractName}" in ${umlClass.absolutePath}`); - const storageSections = []; - const variables = parseVariables(umlClass, umlClasses, [], storageSections, [], false, arrayItems, noExpandVariables); + debug(`Found contract "${contractName}" in ${umlClass.absolutePath}`) + const storageSections = [] + const variables = parseVariables( + umlClass, + umlClasses, + [], + storageSections, + [], + false, + arrayItems, + noExpandVariables, + ) // Add new storage section to the beginning of the array storageSections.unshift({ id: storageId++, name: contractName, type: StorageSectionType.Contract, - variables: variables, + variables, mapping: false, - }); - adjustSlots(storageSections[0], 0, storageSections); - return storageSections; -}; -exports.convertClasses2StorageSections = convertClasses2StorageSections; + }) + adjustSlots(storageSections[0], 0, storageSections) + return storageSections +} +exports.convertClasses2StorageSections = convertClasses2StorageSections const optionStorageVariables = (contractName, slotNames, slotTypes) => { // If no slot names if (!slotNames?.length) { - return []; + return [] } // The slotTypes default should mean this never happens if (!slotTypes.length) { - throw Error(`The slotTypes option must be used with the slotNames option`); + throw Error( + 'The slotTypes option must be used with the slotNames option', + ) } if (slotNames.length > 1 && slotTypes.length === 1) { - slotTypes = Array(slotNames.length).fill(slotTypes[0]); + slotTypes = Array(slotNames.length).fill(slotTypes[0]) // slotTypes = slotTypes.fill(slotTypes[0], 1, slotNames.length - 1) } - const variables = []; + const variables = [] slotNames.forEach((slotName, i) => { - const { size: byteSize, dynamic } = calcElementaryTypeSize(slotTypes[i]); + const { size: byteSize, dynamic } = calcElementaryTypeSize(slotTypes[i]) variables.push({ id: variableId++, fromSlot: undefined, @@ -93,21 +125,21 @@ const optionStorageVariables = (contractName, slotNames, slotTypes) => { contractName, referenceSectionId: undefined, enumValues: undefined, - }); - }); + }) + }) // Sort variables by offset hash const sortedVariables = variables.sort((a, b) => { if (a.offset < b.offset) { - return -1; + return -1 } if (a.offset > b.offset) { - return 1; + return 1 } - return 0; - }); - return sortedVariables; -}; -exports.optionStorageVariables = optionStorageVariables; + return 0 + }) + return sortedVariables +} +exports.optionStorageVariables = optionStorageVariables /** * Recursively parse the storage variables for a given contract or struct. * @param umlClass contract or file level struct @@ -119,55 +151,103 @@ exports.optionStorageVariables = optionStorageVariables; * @param arrayItems the number of items to display at the start and end of an array * @return variables array of storage variables in the `umlClass` */ -const parseVariables = (umlClass, umlClasses, variables, storageSections, inheritedContracts, mapping, arrayItems, noExpandVariables) => { +const parseVariables = ( + umlClass, + umlClasses, + variables, + storageSections, + inheritedContracts, + mapping, + arrayItems, + noExpandVariables, +) => { // Add storage slots from inherited contracts first. // Get immediate parent contracts that the class inherits from - const parentContracts = umlClass.getParentContracts(); + const parentContracts = umlClass.getParentContracts() // Filter out any already inherited contracts - const newInheritedContracts = parentContracts.filter((parentContract) => !inheritedContracts.includes(parentContract.targetUmlClassName)); + const newInheritedContracts = parentContracts.filter( + (parentContract) => + !inheritedContracts.includes(parentContract.targetUmlClassName), + ) // Mutate inheritedContracts to include the new inherited contracts - inheritedContracts.push(...newInheritedContracts.map((c) => c.targetUmlClassName)); + inheritedContracts.push( + ...newInheritedContracts.map((c) => c.targetUmlClassName), + ) // Recursively parse each new inherited contract newInheritedContracts.forEach((parent) => { - const parentClass = (0, associations_1.findAssociatedClass)(parent, umlClass, umlClasses); + const parentClass = (0, associations_1.findAssociatedClass)( + parent, + umlClass, + umlClasses, + ) if (!parentClass) { - throw Error(`Failed to find inherited contract "${parent.targetUmlClassName}" sourced from "${umlClass.name}" with path "${umlClass.absolutePath}"`); + throw Error( + `Failed to find inherited contract "${parent.targetUmlClassName}" sourced from "${umlClass.name}" with path "${umlClass.absolutePath}"`, + ) } // recursively parse inherited contract - parseVariables(parentClass, umlClasses, variables, storageSections, inheritedContracts, mapping, arrayItems, noExpandVariables); - }); + parseVariables( + parentClass, + umlClasses, + variables, + storageSections, + inheritedContracts, + mapping, + arrayItems, + noExpandVariables, + ) + }) // Parse storage for each attribute umlClass.attributes.forEach((attribute) => { // Ignore any attributes that are constants or immutable - if (attribute.compiled) - return; - const { size: byteSize, dynamic } = (0, exports.calcStorageByteSize)(attribute, umlClass, umlClasses); + if (attribute.compiled) { + return + } + const { size: byteSize, dynamic } = (0, exports.calcStorageByteSize)( + attribute, + umlClass, + umlClasses, + ) // parse any dependent storage sections or enums const references = noExpandVariables.includes(attribute.name) ? undefined - : (0, exports.parseStorageSectionFromAttribute)(attribute, umlClass, umlClasses, storageSections, mapping || attribute.attributeType === umlClass_1.AttributeType.Mapping, arrayItems, noExpandVariables); + : (0, exports.parseStorageSectionFromAttribute)( + attribute, + umlClass, + umlClasses, + storageSections, + mapping || + attribute.attributeType === + umlClass_1.AttributeType.Mapping, + arrayItems, + noExpandVariables, + ) // should this new variable get the slot value - const displayValue = calcDisplayValue(attribute.attributeType, dynamic, mapping, references?.storageSection?.type); - const getValue = calcGetValue(attribute.attributeType, mapping); + const displayValue = calcDisplayValue( + attribute.attributeType, + dynamic, + mapping, + references?.storageSection?.type, + ) + const getValue = calcGetValue(attribute.attributeType, mapping) // Get the toSlot of the last storage item - const lastVariable = variables[variables.length - 1]; - let lastToSlot = lastVariable ? lastVariable.toSlot : 0; - let nextOffset = lastVariable + const lastVariable = variables[variables.length - 1] + const lastToSlot = lastVariable ? lastVariable.toSlot : 0 + const nextOffset = lastVariable ? lastVariable.byteOffset + lastVariable.byteSize - : 0; - let fromSlot; - let toSlot; - let byteOffset; + : 0 + let fromSlot + let toSlot + let byteOffset if (nextOffset + byteSize > 32) { - const nextFromSlot = variables.length > 0 ? lastToSlot + 1 : 0; - fromSlot = nextFromSlot; - toSlot = nextFromSlot + Math.floor((byteSize - 1) / 32); - byteOffset = 0; - } - else { - fromSlot = lastToSlot; - toSlot = lastToSlot; - byteOffset = nextOffset; + const nextFromSlot = variables.length > 0 ? lastToSlot + 1 : 0 + fromSlot = nextFromSlot + toSlot = nextFromSlot + Math.floor((byteSize - 1) / 32) + byteOffset = 0 + } else { + fromSlot = lastToSlot + toSlot = lastToSlot + byteOffset = nextOffset } variables.push({ id: variableId++, @@ -184,10 +264,10 @@ const parseVariables = (umlClass, umlClasses, variables, storageSections, inheri contractName: umlClass.name, referenceSectionId: references?.storageSection?.id, enumValues: references?.enumValues, - }); - }); - return variables; -}; + }) + }) + return variables +} /** * Recursively adjusts the fromSlot and toSlot properties of any storage variables * that are referenced by a static array or struct. @@ -199,23 +279,33 @@ const parseVariables = (umlClass, umlClasses, variables, storageSections, inheri const adjustSlots = (storageSection, slotOffset, storageSections) => { storageSection.variables.forEach((variable) => { // offset storage slots - variable.fromSlot += slotOffset; - variable.toSlot += slotOffset; + variable.fromSlot += slotOffset + variable.toSlot += slotOffset // find storage section that the variable is referencing - const referenceStorageSection = storageSections.find((ss) => ss.id === variable.referenceSectionId); + const referenceStorageSection = storageSections.find( + (ss) => ss.id === variable.referenceSectionId, + ) if (referenceStorageSection) { - referenceStorageSection.offset = storageSection.offset; + referenceStorageSection.offset = storageSection.offset if (!variable.dynamic) { - adjustSlots(referenceStorageSection, variable.fromSlot, storageSections); - } - else if (variable.attributeType === umlClass_1.AttributeType.Array) { + adjustSlots( + referenceStorageSection, + variable.fromSlot, + storageSections, + ) + } else if ( + variable.attributeType === umlClass_1.AttributeType.Array + ) { // attribute is a dynamic array - referenceStorageSection.offset = (0, exports.calcSectionOffset)(variable, storageSection.offset); - adjustSlots(referenceStorageSection, 0, storageSections); + referenceStorageSection.offset = (0, exports.calcSectionOffset)( + variable, + storageSection.offset, + ) + adjustSlots(referenceStorageSection, 0, storageSections) } } - }); -}; + }) +} /** * Recursively adds new storage sections under a class attribute. * also returns the allowed enum values @@ -228,48 +318,76 @@ const adjustSlots = (storageSection, slotOffset, storageSections) => { * @return storageSection new storage section that was added or undefined if none was added. * @return enumValues array of allowed enum values. undefined if attribute is not an enum */ -const parseStorageSectionFromAttribute = (attribute, umlClass, otherClasses, storageSections, mapping, arrayItems, noExpandVariables) => { +const parseStorageSectionFromAttribute = ( + attribute, + umlClass, + otherClasses, + storageSections, + mapping, + arrayItems, + noExpandVariables, +) => { if (attribute.attributeType === umlClass_1.AttributeType.Array) { // storage is dynamic if the attribute type ends in [] - const result = attribute.type.match(/\[([\w$.]*)]$/); - const dynamic = result[1] === ''; + const result = attribute.type.match(/\[([\w$.]*)]$/) + const dynamic = result[1] === '' const arrayLength = !dynamic - ? (0, exports.findDimensionLength)(umlClass, result[1], otherClasses) - : undefined; + ? (0, exports.findDimensionLength)( + umlClass, + result[1], + otherClasses, + ) + : undefined // get the type of the array items. eg // address[][4][2] will have base type address[][4] - const baseType = attribute.type.substring(0, attribute.type.lastIndexOf('[')); - let baseAttributeType; + const baseType = attribute.type.substring( + 0, + attribute.type.lastIndexOf('['), + ) + let baseAttributeType if ((0, exports.isElementary)(baseType)) { - baseAttributeType = umlClass_1.AttributeType.Elementary; - } - else if (baseType[baseType.length - 1] === ']') { - baseAttributeType = umlClass_1.AttributeType.Array; - } - else { - baseAttributeType = umlClass_1.AttributeType.UserDefined; + baseAttributeType = umlClass_1.AttributeType.Elementary + } else if (baseType[baseType.length - 1] === ']') { + baseAttributeType = umlClass_1.AttributeType.Array + } else { + baseAttributeType = umlClass_1.AttributeType.UserDefined } const baseAttribute = { visibility: attribute.visibility, name: attribute.name, type: baseType, attributeType: baseAttributeType, - }; - const { size: arrayItemSize, dynamic: dynamicBase } = (0, exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses); + } + const { size: arrayItemSize, dynamic: dynamicBase } = (0, + exports.calcStorageByteSize)(baseAttribute, umlClass, otherClasses) // If more than 16 bytes, then round up in 32 bytes increments - const arraySlotSize = arrayItemSize > 16 - ? 32 * Math.ceil(arrayItemSize / 32) - : arrayItemSize; + const arraySlotSize = + arrayItemSize > 16 + ? 32 * Math.ceil(arrayItemSize / 32) + : arrayItemSize // If base type is not an Elementary type // This can only be Array and UserDefined for base types of arrays. - let references; + let references if (baseAttributeType !== umlClass_1.AttributeType.Elementary) { // recursively add storage section for Array and UserDefined types - references = (0, exports.parseStorageSectionFromAttribute)(baseAttribute, umlClass, otherClasses, storageSections, mapping, arrayItems, noExpandVariables); + references = (0, exports.parseStorageSectionFromAttribute)( + baseAttribute, + umlClass, + otherClasses, + storageSections, + mapping, + arrayItems, + noExpandVariables, + ) } - const displayValue = calcDisplayValue(baseAttribute.attributeType, dynamicBase, mapping, references?.storageSection?.type); - const getValue = calcGetValue(attribute.attributeType, mapping); - const variables = []; + const displayValue = calcDisplayValue( + baseAttribute.attributeType, + dynamicBase, + mapping, + references?.storageSection?.type, + ) + const getValue = calcGetValue(attribute.attributeType, mapping) + const variables = [] variables[0] = { id: variableId++, fromSlot: 0, @@ -283,24 +401,33 @@ const parseStorageSectionFromAttribute = (attribute, umlClass, otherClasses, sto displayValue, referenceSectionId: references?.storageSection?.id, enumValues: references?.enumValues, - }; + } // If a fixed size array. // Note dynamic arrays will have undefined arrayLength if (arrayLength > 1) { // Add missing fixed array variables from index 1 - addArrayVariables(arrayLength, arrayItems, variables); + addArrayVariables(arrayLength, arrayItems, variables) // For the newly added variables variables.forEach((variable, i) => { - if (i > 0 && + if ( + i > 0 && baseAttributeType !== umlClass_1.AttributeType.Elementary && variable.type !== '----' // ignore any filler variables ) { // recursively add storage section for Array and UserDefined types - references = (0, exports.parseStorageSectionFromAttribute)(baseAttribute, umlClass, otherClasses, storageSections, mapping, arrayItems, noExpandVariables); - variable.referenceSectionId = references?.storageSection?.id; - variable.enumValues = references?.enumValues; + references = (0, exports.parseStorageSectionFromAttribute)( + baseAttribute, + umlClass, + otherClasses, + storageSections, + mapping, + arrayItems, + noExpandVariables, + ) + variable.referenceSectionId = references?.storageSection?.id + variable.enumValues = references?.enumValues } - }); + }) } const storageSection = { id: storageId++, @@ -310,60 +437,91 @@ const parseStorageSectionFromAttribute = (attribute, umlClass, otherClasses, sto arrayLength, variables, mapping, - }; - storageSections.push(storageSection); - return { storageSection }; + } + storageSections.push(storageSection) + return { storageSection } } if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) { // Is the user defined type linked to another Contract, Struct or Enum? - const typeClass = findTypeClass(attribute.type, attribute, umlClass, otherClasses); + const typeClass = findTypeClass( + attribute.type, + attribute, + umlClass, + otherClasses, + ) if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) { - const variables = parseVariables(typeClass, otherClasses, [], storageSections, [], mapping, arrayItems, noExpandVariables); + const variables = parseVariables( + typeClass, + otherClasses, + [], + storageSections, + [], + mapping, + arrayItems, + noExpandVariables, + ) const storageSection = { id: storageId++, name: attribute.type, type: StorageSectionType.Struct, variables, mapping, - }; - storageSections.push(storageSection); - return { storageSection }; - } - else if (typeClass.stereotype === umlClass_1.ClassStereotype.Enum) { + } + storageSections.push(storageSection) + return { storageSection } + } else if (typeClass.stereotype === umlClass_1.ClassStereotype.Enum) { return { storageSection: undefined, enumValues: typeClass.attributes.map((a) => a.name), - }; + } } - return undefined; + return undefined } if (attribute.attributeType === umlClass_1.AttributeType.Mapping) { // get the UserDefined type from the mapping // note the mapping could be an array of Structs // Could also be a mapping of a mapping - const result = attribute.type.match(/=\\>((?!mapping)[\w$.]*)[\\[]/); + const result = attribute.type.match(/=\\>((?!mapping)[\w$.]*)[\\[]/) // If mapping of user defined type - if (result !== null && result[1] && !(0, exports.isElementary)(result[1])) { + if ( + result !== null && + result[1] && + !(0, exports.isElementary)(result[1]) + ) { // Find UserDefined type can be a contract, struct or enum - const typeClass = findTypeClass(result[1], attribute, umlClass, otherClasses); + const typeClass = findTypeClass( + result[1], + attribute, + umlClass, + otherClasses, + ) if (typeClass.stereotype === umlClass_1.ClassStereotype.Struct) { - let variables = parseVariables(typeClass, otherClasses, [], storageSections, [], true, arrayItems, noExpandVariables); + const variables = parseVariables( + typeClass, + otherClasses, + [], + storageSections, + [], + true, + arrayItems, + noExpandVariables, + ) const storageSection = { id: storageId++, name: typeClass.name, type: StorageSectionType.Struct, mapping: true, variables, - }; - storageSections.push(storageSection); - return { storageSection }; + } + storageSections.push(storageSection) + return { storageSection } } } - return undefined; + return undefined } - return undefined; -}; -exports.parseStorageSectionFromAttribute = parseStorageSectionFromAttribute; + return undefined +} +exports.parseStorageSectionFromAttribute = parseStorageSectionFromAttribute /** * Adds missing storage variables to a fixed-size or dynamic array by cloning them from the first variable. * @param arrayLength the length of the array @@ -371,42 +529,48 @@ exports.parseStorageSectionFromAttribute = parseStorageSectionFromAttribute; * @param variables mutable array of storage variables that are appended to */ const addArrayVariables = (arrayLength, arrayItems, variables) => { - const arraySlotSize = variables[0].byteSize; - const itemsPerSlot = Math.floor(32 / arraySlotSize); - const slotsPerItem = Math.ceil(arraySlotSize / 32); - const firstFillerItem = itemsPerSlot > 0 ? arrayItems * itemsPerSlot : arrayItems; - const lastFillerItem = itemsPerSlot > 0 - ? arrayLength - - (arrayItems - 1) * itemsPerSlot - // the number of items in all but the last row - (arrayLength % itemsPerSlot || itemsPerSlot) - // the remaining items in the last row or all the items in a slot - 1 // need the items before the last three rows - : arrayLength - arrayItems - 1; + const arraySlotSize = variables[0].byteSize + const itemsPerSlot = Math.floor(32 / arraySlotSize) + const slotsPerItem = Math.ceil(arraySlotSize / 32) + const firstFillerItem = + itemsPerSlot > 0 ? arrayItems * itemsPerSlot : arrayItems + const lastFillerItem = + itemsPerSlot > 0 + ? arrayLength - + (arrayItems - 1) * itemsPerSlot - // the number of items in all but the last row + (arrayLength % itemsPerSlot || itemsPerSlot) - // the remaining items in the last row or all the items in a slot + 1 // need the items before the last three rows + : arrayLength - arrayItems - 1 // Add variable from index 1 for each item in the array for (let i = 1; i < arrayLength; i++) { - const fromSlot = itemsPerSlot > 0 ? Math.floor(i / itemsPerSlot) : i * slotsPerItem; - const toSlot = itemsPerSlot > 0 ? fromSlot : fromSlot + slotsPerItem; + const fromSlot = + itemsPerSlot > 0 ? Math.floor(i / itemsPerSlot) : i * slotsPerItem + const toSlot = itemsPerSlot > 0 ? fromSlot : fromSlot + slotsPerItem // add filler variable before adding the first of the last items of the array if (i === lastFillerItem && firstFillerItem < lastFillerItem) { - const fillerFromSlot = itemsPerSlot > 0 - ? Math.floor(firstFillerItem / itemsPerSlot) - : firstFillerItem * slotsPerItem; + const fillerFromSlot = + itemsPerSlot > 0 + ? Math.floor(firstFillerItem / itemsPerSlot) + : firstFillerItem * slotsPerItem variables.push({ id: variableId++, attributeType: umlClass_1.AttributeType.UserDefined, type: '----', fromSlot: fillerFromSlot, - toSlot: toSlot, + toSlot, byteOffset: 0, byteSize: (toSlot - fillerFromSlot + 1) * 32, getValue: false, displayValue: false, dynamic: false, - }); + }) } // Add variables for the first arrayItems and last arrayItems if (i < firstFillerItem || i > lastFillerItem) { - const byteOffset = itemsPerSlot > 0 ? (i % itemsPerSlot) * arraySlotSize : 0; - const slotValue = fromSlot === 0 ? variables[0].slotValue : undefined; + const byteOffset = + itemsPerSlot > 0 ? (i % itemsPerSlot) * arraySlotSize : 0 + const slotValue = + fromSlot === 0 ? variables[0].slotValue : undefined // add array variable const newVariable = { ...variables[0], @@ -419,12 +583,12 @@ const addArrayVariables = (arrayLength, arrayItems, variables) => { parsedValue: undefined, referenceSectionId: undefined, enumValues: undefined, - }; - newVariable.parsedValue = (0, slotValues_1.parseValue)(newVariable); - variables.push(newVariable); + } + newVariable.parsedValue = (0, slotValues_1.parseValue)(newVariable) + variables.push(newVariable) } } -}; +} /** * Finds an attribute's user defined type that can be a Contract, Struct or Enum * @param userType User defined type that is being looked for. This can be the base type of an attribute. @@ -434,180 +598,226 @@ const addArrayVariables = (arrayLength, arrayItems, variables) => { */ const findTypeClass = (userType, attribute, umlClass, otherClasses) => { // Find associated UserDefined type - const types = userType.split('.'); + const types = userType.split('.') const association = { referenceType: umlClass_1.ReferenceType.Memory, targetUmlClassName: types.length === 1 ? types[0] : types[1], parentUmlClassName: types.length === 1 ? undefined : types[0], - }; - const typeClass = (0, associations_1.findAssociatedClass)(association, umlClass, otherClasses); + } + const typeClass = (0, associations_1.findAssociatedClass)( + association, + umlClass, + otherClasses, + ) if (!typeClass) { - throw Error(`Failed to find user defined type "${userType}" in attribute "${attribute.name}" of from class "${umlClass.name}" with path "${umlClass.absolutePath}"`); + throw Error( + `Failed to find user defined type "${userType}" in attribute "${attribute.name}" of from class "${umlClass.name}" with path "${umlClass.absolutePath}"`, + ) } - return typeClass; -}; + return typeClass +} // Calculates the storage size of an attribute in bytes const calcStorageByteSize = (attribute, umlClass, otherClasses) => { - if (attribute.attributeType === umlClass_1.AttributeType.Mapping || - attribute.attributeType === umlClass_1.AttributeType.Function) { - return { size: 32, dynamic: true }; + if ( + attribute.attributeType === umlClass_1.AttributeType.Mapping || + attribute.attributeType === umlClass_1.AttributeType.Function + ) { + return { size: 32, dynamic: true } } if (attribute.attributeType === umlClass_1.AttributeType.Array) { // Fixed sized arrays are read from right to left until there is a dynamic dimension // eg address[][3][2] is a fixed size array that uses 6 slots. // while address [2][] is a dynamic sized array. - const arrayDimensions = attribute.type.match(/\[[\w$.]*]/g); + const arrayDimensions = attribute.type.match(/\[[\w$.]*]/g) // Remove first [ and last ] from each arrayDimensions - const dimensionsStr = arrayDimensions.map((a) => a.slice(1, -1)); + const dimensionsStr = arrayDimensions.map((a) => a.slice(1, -1)) // fixed-sized arrays are read from right to left so reverse the dimensions - const dimensionsStrReversed = dimensionsStr.reverse(); + const dimensionsStrReversed = dimensionsStr.reverse() // read fixed-size dimensions until we get a dynamic array with no dimension - let dimension = dimensionsStrReversed.shift(); - const fixedDimensions = []; + let dimension = dimensionsStrReversed.shift() + const fixedDimensions = [] while (dimension && dimension !== '') { - const dimensionNum = (0, exports.findDimensionLength)(umlClass, dimension, otherClasses); - fixedDimensions.push(dimensionNum); + const dimensionNum = (0, exports.findDimensionLength)( + umlClass, + dimension, + otherClasses, + ) + fixedDimensions.push(dimensionNum) // read the next dimension for the next loop - dimension = dimensionsStrReversed.shift(); + dimension = dimensionsStrReversed.shift() } // If the first dimension is dynamic, ie [] if (fixedDimensions.length === 0) { // dynamic arrays start at the keccak256 of the slot number // the array length is stored in the 32 byte slot - return { size: 32, dynamic: true }; + return { size: 32, dynamic: true } } // If a fixed sized array - let elementSize; - const type = attribute.type.substring(0, attribute.type.indexOf('[')); + let elementSize + const type = attribute.type.substring(0, attribute.type.indexOf('[')) if ((0, exports.isElementary)(type)) { const elementAttribute = { attributeType: umlClass_1.AttributeType.Elementary, type, name: 'element', - }; - ({ size: elementSize } = (0, exports.calcStorageByteSize)(elementAttribute, umlClass, otherClasses)); - } - else { + } + ;({ size: elementSize } = (0, exports.calcStorageByteSize)( + elementAttribute, + umlClass, + otherClasses, + )) + } else { const elementAttribute = { attributeType: umlClass_1.AttributeType.UserDefined, type, name: 'userDefined', - }; - ({ size: elementSize } = (0, exports.calcStorageByteSize)(elementAttribute, umlClass, otherClasses)); + } + ;({ size: elementSize } = (0, exports.calcStorageByteSize)( + elementAttribute, + umlClass, + otherClasses, + )) } // Anything over 16 bytes, like an address, will take a whole 32 byte slot if (elementSize > 16 && elementSize < 32) { - elementSize = 32; + elementSize = 32 } // If multi dimension, then the first element is 32 bytes if (fixedDimensions.length < arrayDimensions.length) { - const totalDimensions = fixedDimensions.reduce((total, dimension) => total * dimension, 1); + const totalDimensions = fixedDimensions.reduce( + (total, dimension) => total * dimension, + 1, + ) return { size: 32 * totalDimensions, dynamic: false, - }; + } } - const lastItem = fixedDimensions.length - 1; - const lastArrayLength = fixedDimensions[lastItem]; - const itemsPerSlot = Math.floor(32 / elementSize); - const lastDimensionBytes = itemsPerSlot > 0 // if one or more array items in a slot - ? Math.ceil(lastArrayLength / itemsPerSlot) * 32 // round up to include unallocated slot space - : elementSize * fixedDimensions[lastItem]; - const lastDimensionSlotBytes = Math.ceil(lastDimensionBytes / 32) * 32; + const lastItem = fixedDimensions.length - 1 + const lastArrayLength = fixedDimensions[lastItem] + const itemsPerSlot = Math.floor(32 / elementSize) + const lastDimensionBytes = + itemsPerSlot > 0 // if one or more array items in a slot + ? Math.ceil(lastArrayLength / itemsPerSlot) * 32 // round up to include unallocated slot space + : elementSize * fixedDimensions[lastItem] + const lastDimensionSlotBytes = Math.ceil(lastDimensionBytes / 32) * 32 const remainingDimensions = fixedDimensions .slice(0, lastItem) - .reduce((total, dimension) => total * dimension, 1); + .reduce((total, dimension) => total * dimension, 1) return { size: lastDimensionSlotBytes * remainingDimensions, dynamic: false, - }; + } } // If a Struct, Enum or Contract reference // TODO need to handle User Defined Value Types when they are added to Solidity if (attribute.attributeType === umlClass_1.AttributeType.UserDefined) { // Is the user defined type linked to another Contract, Struct or Enum? - const attributeTypeClass = findTypeClass(attribute.type, attribute, umlClass, otherClasses); + const attributeTypeClass = findTypeClass( + attribute.type, + attribute, + umlClass, + otherClasses, + ) switch (attributeTypeClass.stereotype) { case umlClass_1.ClassStereotype.Enum: - return { size: 1, dynamic: false }; + return { size: 1, dynamic: false } case umlClass_1.ClassStereotype.Contract: case umlClass_1.ClassStereotype.Abstract: case umlClass_1.ClassStereotype.Interface: case umlClass_1.ClassStereotype.Library: - return { size: 20, dynamic: false }; + return { size: 20, dynamic: false } case umlClass_1.ClassStereotype.Struct: - let structByteSize = 0; + let structByteSize = 0 attributeTypeClass.attributes.forEach((structAttribute) => { // If next attribute is an array, then we need to start in a new slot - if (structAttribute.attributeType === umlClass_1.AttributeType.Array) { - structByteSize = Math.ceil(structByteSize / 32) * 32; + if ( + structAttribute.attributeType === + umlClass_1.AttributeType.Array + ) { + structByteSize = Math.ceil(structByteSize / 32) * 32 } // If next attribute is an struct, then we need to start in a new slot - else if (structAttribute.attributeType === - umlClass_1.AttributeType.UserDefined) { + else if ( + structAttribute.attributeType === + umlClass_1.AttributeType.UserDefined + ) { // UserDefined types can be a struct or enum, so we need to check if it's a struct - const userDefinedClass = findTypeClass(structAttribute.type, structAttribute, umlClass, otherClasses); + const userDefinedClass = findTypeClass( + structAttribute.type, + structAttribute, + umlClass, + otherClasses, + ) // If a struct - if (userDefinedClass.stereotype === - umlClass_1.ClassStereotype.Struct) { - structByteSize = Math.ceil(structByteSize / 32) * 32; + if ( + userDefinedClass.stereotype === + umlClass_1.ClassStereotype.Struct + ) { + structByteSize = Math.ceil(structByteSize / 32) * 32 } } - const { size: attributeSize } = (0, exports.calcStorageByteSize)(structAttribute, umlClass, otherClasses); + const { size: attributeSize } = (0, + exports.calcStorageByteSize)( + structAttribute, + umlClass, + otherClasses, + ) // check if attribute will fit into the remaining slot - const endCurrentSlot = Math.ceil(structByteSize / 32) * 32; - const spaceLeftInSlot = endCurrentSlot - structByteSize; + const endCurrentSlot = Math.ceil(structByteSize / 32) * 32 + const spaceLeftInSlot = endCurrentSlot - structByteSize if (attributeSize <= spaceLeftInSlot) { - structByteSize += attributeSize; + structByteSize += attributeSize + } else { + structByteSize = endCurrentSlot + attributeSize } - else { - structByteSize = endCurrentSlot + attributeSize; - } - }); + }) // structs take whole 32 byte slots so round up to the nearest 32 sized slots return { size: Math.ceil(structByteSize / 32) * 32, dynamic: false, - }; + } default: - return { size: 20, dynamic: false }; + return { size: 20, dynamic: false } } } if (attribute.attributeType === umlClass_1.AttributeType.Elementary) { - return calcElementaryTypeSize(attribute.type); + return calcElementaryTypeSize(attribute.type) } - throw new Error(`Failed to calc bytes size of attribute with name "${attribute.name}" and type ${attribute.type}`); -}; -exports.calcStorageByteSize = calcStorageByteSize; + throw new Error( + `Failed to calc bytes size of attribute with name "${attribute.name}" and type ${attribute.type}`, + ) +} +exports.calcStorageByteSize = calcStorageByteSize const calcElementaryTypeSize = (type) => { switch (type) { case 'bool': - return { size: 1, dynamic: false }; + return { size: 1, dynamic: false } case 'address': - return { size: 20, dynamic: false }; + return { size: 20, dynamic: false } case 'string': case 'bytes': - return { size: 32, dynamic: true }; + return { size: 32, dynamic: true } case 'uint': case 'int': case 'ufixed': case 'fixed': - return { size: 32, dynamic: false }; + return { size: 32, dynamic: false } default: - const result = type.match(/[u]*(int|fixed|bytes)([0-9]+)/); + const result = type.match(/[u]*(int|fixed|bytes)([0-9]+)/) if (result === null || !result[2]) { - throw Error(`Failed size elementary type "${type}"`); + throw Error(`Failed size elementary type "${type}"`) } // If bytes if (result[1] === 'bytes') { - return { size: parseInt(result[2]), dynamic: false }; + return { size: parseInt(result[2]), dynamic: false } } // TODO need to handle fixed types when they are supported // If an int - const bitSize = parseInt(result[2]); - return { size: bitSize / 8, dynamic: false }; + const bitSize = parseInt(result[2]) + return { size: bitSize / 8, dynamic: false } } -}; +} const isElementary = (type) => { switch (type) { case 'bool': @@ -618,40 +828,54 @@ const isElementary = (type) => { case 'int': case 'ufixed': case 'fixed': - return true; + return true default: - const result = type.match(/^[u]?(int|fixed|bytes)([0-9]+)$/); - return result !== null; + const result = type.match(/^[u]?(int|fixed|bytes)([0-9]+)$/) + return result !== null } -}; -exports.isElementary = isElementary; +} +exports.isElementary = isElementary const calcSectionOffset = (variable, sectionOffset = '0') => { if (variable.dynamic) { - const hexStringOf32Bytes = (0, utils_1.hexZeroPad)(ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString(), 32); - return (0, utils_1.keccak256)(hexStringOf32Bytes); + const hexStringOf32Bytes = (0, utils_1.hexZeroPad)( + ethers_1.BigNumber.from(variable.fromSlot) + .add(sectionOffset) + .toHexString(), + 32, + ) + return (0, utils_1.keccak256)(hexStringOf32Bytes) } - return ethers_1.BigNumber.from(variable.fromSlot).add(sectionOffset).toHexString(); -}; -exports.calcSectionOffset = calcSectionOffset; + return ethers_1.BigNumber.from(variable.fromSlot) + .add(sectionOffset) + .toHexString() +} +exports.calcSectionOffset = calcSectionOffset const findDimensionLength = (umlClass, dimension, otherClasses) => { - const dimensionNum = parseInt(dimension); + const dimensionNum = parseInt(dimension) if (Number.isInteger(dimensionNum)) { - return dimensionNum; + return dimensionNum } // Try and size array dimension from declared constants - const constant = umlClass.constants.find((constant) => constant.name === dimension); + const constant = umlClass.constants.find( + (constant) => constant.name === dimension, + ) if (constant) { - return constant.value; + return constant.value } // Try and size array dimension from file constants - const fileConstant = otherClasses.find((umlClass) => umlClass.name === dimension && - umlClass.stereotype === umlClass_1.ClassStereotype.Constant); + const fileConstant = otherClasses.find( + (umlClass) => + umlClass.name === dimension && + umlClass.stereotype === umlClass_1.ClassStereotype.Constant, + ) if (fileConstant?.constants[0]?.value) { - return fileConstant.constants[0].value; + return fileConstant.constants[0].value } - throw Error(`Could not size fixed sized array with dimension "${dimension}"`); -}; -exports.findDimensionLength = findDimensionLength; + throw Error( + `Could not size fixed sized array with dimension "${dimension}"`, + ) +} +exports.findDimensionLength = findDimensionLength /** * Calculate if the storage slot value for the attribute should be displayed in the storage section. * @@ -669,11 +893,17 @@ exports.findDimensionLength = findDimensionLength; * @param storageSectionType * @return displayValue true if the slot value should be displayed. */ -const calcDisplayValue = (attributeType, dynamic, mapping, storageSectionType) => mapping === false && +const calcDisplayValue = ( + attributeType, + dynamic, + mapping, + storageSectionType, +) => + mapping === false && (attributeType === umlClass_1.AttributeType.Elementary || (attributeType === umlClass_1.AttributeType.UserDefined && storageSectionType !== StorageSectionType.Struct) || - (attributeType === umlClass_1.AttributeType.Array && dynamic)); + (attributeType === umlClass_1.AttributeType.Array && dynamic)) /** * Calculate if the storage slot value for the attribute should be retrieved from the chain. * @@ -687,7 +917,8 @@ const calcDisplayValue = (attributeType, dynamic, mapping, storageSectionType) = * @param mapping flags if the storage section is referenced by a mapping * @return getValue true if the slot value should be retrieved. */ -const calcGetValue = (attributeType, mapping) => mapping === false && attributeType !== umlClass_1.AttributeType.Mapping; +const calcGetValue = (attributeType, mapping) => + mapping === false && attributeType !== umlClass_1.AttributeType.Mapping /** * Recursively adds variables for dynamic string, bytes or arrays * @param storageSection @@ -697,25 +928,36 @@ const calcGetValue = (attributeType, mapping) => mapping === false && attributeT * @param arrayItems the number of items to display at the start and end of an array * @param blockTag block number or `latest` */ -const addDynamicVariables = async (storageSection, storageSections, url, contractAddress, arrayItems, blockTag) => { +const addDynamicVariables = async ( + storageSection, + storageSections, + url, + contractAddress, + arrayItems, + blockTag, +) => { for (const variable of storageSection.variables) { try { - if (!variable.dynamic) - continue; + if (!variable.dynamic) { + continue + } // STEP 1 - add slots for dynamic string and bytes if (variable.type === 'string' || variable.type === 'bytes') { if (!variable.slotValue) { - debug(`WARNING: Variable "${variable.name}" of type "${variable.type}" has no slot value`); - continue; + debug( + `WARNING: Variable "${variable.name}" of type "${variable.type}" has no slot value`, + ) + continue } - const size = (0, slotValues_1.dynamicSlotSize)(variable); + const size = (0, slotValues_1.dynamicSlotSize)(variable) if (size > 31) { - const maxSlotNumber = Math.floor((size - 1) / 32); - const variables = []; + const maxSlotNumber = Math.floor((size - 1) / 32) + const variables = [] // For each dynamic slot for (let i = 0; i <= maxSlotNumber; i++) { // If the last slot then get the remaining bytes - const byteSize = i === maxSlotNumber ? size - 32 * maxSlotNumber : 32; + const byteSize = + i === maxSlotNumber ? size - 32 * maxSlotNumber : 32 // Add variable for the slot variables.push({ id: variableId++, @@ -729,16 +971,16 @@ const addDynamicVariables = async (storageSection, storageSections, url, contrac dynamic: false, getValue: true, displayValue: true, - }); + }) } // add unallocated variable - const unusedBytes = 32 - (size - 32 * maxSlotNumber); + const unusedBytes = 32 - (size - 32 * maxSlotNumber) if (unusedBytes > 0) { - const lastVariable = variables[variables.length - 1]; + const lastVariable = variables[variables.length - 1] variables.push({ ...lastVariable, byteOffset: unusedBytes, - }); + }) variables[maxSlotNumber] = { id: variableId++, fromSlot: maxSlotNumber, @@ -752,46 +994,75 @@ const addDynamicVariables = async (storageSection, storageSections, url, contrac dynamic: false, getValue: true, displayValue: false, - }; + } } const newStorageSection = { id: storageId++, name: `${variable.type}: ${variable.name}`, - offset: (0, exports.calcSectionOffset)(variable, storageSection.offset), - type: variable.type === 'string' - ? StorageSectionType.String - : StorageSectionType.Bytes, + offset: (0, exports.calcSectionOffset)( + variable, + storageSection.offset, + ), + type: + variable.type === 'string' + ? StorageSectionType.String + : StorageSectionType.Bytes, arrayDynamic: true, arrayLength: size, variables, mapping: false, - }; - variable.referenceSectionId = newStorageSection.id; + } + variable.referenceSectionId = newStorageSection.id // get slot values for new referenced dynamic string or bytes - await (0, slotValues_1.addSlotValues)(url, contractAddress, newStorageSection, arrayItems, blockTag); - storageSections.push(newStorageSection); + await (0, slotValues_1.addSlotValues)( + url, + contractAddress, + newStorageSection, + arrayItems, + blockTag, + ) + storageSections.push(newStorageSection) } - continue; + continue + } + if (variable.attributeType !== umlClass_1.AttributeType.Array) { + continue } - if (variable.attributeType !== umlClass_1.AttributeType.Array) - continue; // STEP 2 - add slots for dynamic arrays // find storage section that the variable is referencing - const referenceStorageSection = storageSections.find((ss) => ss.id === variable.referenceSectionId); - if (!referenceStorageSection) - continue; + const referenceStorageSection = storageSections.find( + (ss) => ss.id === variable.referenceSectionId, + ) + if (!referenceStorageSection) { + continue + } // recursively add dynamic variables to referenced array. // this could be a fixed-size or dynamic array - await (0, exports.addDynamicVariables)(referenceStorageSection, storageSections, url, contractAddress, arrayItems, blockTag); + await (0, exports.addDynamicVariables)( + referenceStorageSection, + storageSections, + url, + contractAddress, + arrayItems, + blockTag, + ) if (!variable.slotValue) { - debug(`WARNING: Dynamic array variable "${variable.name}" of type "${variable.type}" has no slot value`); - continue; + debug( + `WARNING: Dynamic array variable "${variable.name}" of type "${variable.type}" has no slot value`, + ) + continue } // Add missing dynamic array variables - const arrayLength = ethers_1.BigNumber.from(variable.slotValue).toNumber(); + const arrayLength = ethers_1.BigNumber.from( + variable.slotValue, + ).toNumber() if (arrayLength > 1) { // Add missing array variables to the referenced dynamic array - addArrayVariables(arrayLength, arrayItems, referenceStorageSection.variables); + addArrayVariables( + arrayLength, + arrayItems, + referenceStorageSection.variables, + ) // // For the newly added variables // referenceStorageSection.variables.forEach((variable, i) => { // if ( @@ -814,12 +1085,20 @@ const addDynamicVariables = async (storageSection, storageSections, url, contrac // }) } // Get missing slot values to the referenced dynamic array - await (0, slotValues_1.addSlotValues)(url, contractAddress, referenceStorageSection, arrayItems, blockTag); - } - catch (err) { - throw Error(`Failed to add dynamic vars for section "${storageSection.name}", var type "${variable.type}" with value "${variable.slotValue}" from slot ${variable.fromSlot} and section offset ${storageSection.offset}`, { cause: err }); + await (0, slotValues_1.addSlotValues)( + url, + contractAddress, + referenceStorageSection, + arrayItems, + blockTag, + ) + } catch (err) { + throw Error( + `Failed to add dynamic vars for section "${storageSection.name}", var type "${variable.type}" with value "${variable.slotValue}" from slot ${variable.fromSlot} and section offset ${storageSection.offset}`, + { cause: err }, + ) } } -}; -exports.addDynamicVariables = addDynamicVariables; -//# sourceMappingURL=converterClasses2Storage.js.map \ No newline at end of file +} +exports.addDynamicVariables = addDynamicVariables +// # sourceMappingURL=converterClasses2Storage.js.map diff --git a/lib/converterStorage2Dot.d.ts b/lib/converterStorage2Dot.d.ts index b0061c94..c0e7f67f 100644 --- a/lib/converterStorage2Dot.d.ts +++ b/lib/converterStorage2Dot.d.ts @@ -1,13 +1,20 @@ -import { StorageSection } from './converterClasses2Storage'; -export declare const convertStorages2Dot: (storageSections: readonly StorageSection[], options: { - data: boolean; - backColor: string; - shapeColor: string; - fillColor: string; - textColor: string; - hideValues?: boolean; -}) => string; -export declare function convertStorage2Dot(storageSection: StorageSection, dotString: string, options: { - data: boolean; - hideValues?: boolean; -}): string; +import { StorageSection } from './converterClasses2Storage' +export declare const convertStorages2Dot: ( + storageSections: readonly StorageSection[], + options: { + data: boolean + backColor: string + shapeColor: string + fillColor: string + textColor: string + hideValues?: boolean + }, +) => string +export declare function convertStorage2Dot( + storageSection: StorageSection, + dotString: string, + options: { + data: boolean + hideValues?: boolean + }, +): string diff --git a/lib/converterStorage2Dot.js b/lib/converterStorage2Dot.js index f0f6958e..d742b503 100644 --- a/lib/converterStorage2Dot.js +++ b/lib/converterStorage2Dot.js @@ -1,10 +1,10 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.convertStorage2Dot = exports.convertStorages2Dot = void 0; -const converterClasses2Storage_1 = require("./converterClasses2Storage"); -const umlClass_1 = require("./umlClass"); -const formatters_1 = require("./utils/formatters"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.convertStorage2Dot = exports.convertStorages2Dot = void 0 +const converterClasses2Storage_1 = require('./converterClasses2Storage') +const umlClass_1 = require('./umlClass') +const formatters_1 = require('./utils/formatters') +const debug = require('debug')('sol2uml') const convertStorages2Dot = (storageSections, options) => { let dotString = ` digraph StorageDiagram { @@ -12,74 +12,83 @@ rankdir=LR arrowhead=open bgcolor="${options.backColor}" edge [color="${options.shapeColor}"] -node [shape=record, style=filled, color="${options.shapeColor}", fillcolor="${options.fillColor}", fontcolor="${options.textColor}", fontname="Courier New"]`; +node [shape=record, style=filled, color="${options.shapeColor}", fillcolor="${options.fillColor}", fontcolor="${options.textColor}", fontname="Courier New"]` // process contract and the struct storages storageSections.forEach((storage) => { - dotString = convertStorage2Dot(storage, dotString, options); - }); + dotString = convertStorage2Dot(storage, dotString, options) + }) // link contract and structs to structs storageSections.forEach((slot) => { slot.variables.forEach((storage) => { if (storage.referenceSectionId) { - dotString += `\n ${slot.id}:${storage.id} -> ${storage.referenceSectionId}`; + dotString += `\n ${slot.id}:${storage.id} -> ${storage.referenceSectionId}` } - }); - }); + }) + }) // Need to close off the last digraph - dotString += '\n}'; - debug(dotString); - return dotString; -}; -exports.convertStorages2Dot = convertStorages2Dot; + dotString += '\n}' + debug(dotString) + return dotString +} +exports.convertStorages2Dot = convertStorages2Dot function convertStorage2Dot(storageSection, dotString, options) { // write storage header with name and optional address - dotString += `\n${storageSection.id} [label="${storageSection.name} \\<\\<${storageSection.type}\\>\\>\\n${storageSection.address || storageSection.offset || ''}`; - dotString += ' | {'; - const startingVariables = storageSection.variables.filter((s) => s.byteOffset === 0); + dotString += `\n${storageSection.id} [label="${storageSection.name} \\<\\<${storageSection.type}\\>\\>\\n${storageSection.address || storageSection.offset || ''}` + dotString += ' | {' + const startingVariables = storageSection.variables.filter( + (s) => s.byteOffset === 0, + ) // for each slot displayed, does is have any variables with parsed data? - const displayData = startingVariables.map((startVar) => storageSection.variables.some((variable) => variable.fromSlot === startVar.fromSlot && variable.parsedValue)); - const linePad = '\\n\\ '; + const displayData = startingVariables.map((startVar) => + storageSection.variables.some( + (variable) => + variable.fromSlot === startVar.fromSlot && variable.parsedValue, + ), + ) + const linePad = '\\n\\ ' // write slot numbers - const dataLine = options.data ? linePad : ''; + const dataLine = options.data ? linePad : '' dotString += storageSection.offset || storageSection.mapping ? `{ offset${dataLine}` - : `{ slot${dataLine}`; + : `{ slot${dataLine}` startingVariables.forEach((variable, i) => { - const dataLine = options.data && displayData[i] ? linePad : ''; + const dataLine = options.data && displayData[i] ? linePad : '' if (variable.offset) { - dotString += ` | ${(0, formatters_1.shortBytes32)(variable.offset)}${dataLine}`; - } - else if (variable.fromSlot === variable.toSlot) { - dotString += ` | ${variable.fromSlot}${dataLine}`; - } - else { - dotString += ` | ${variable.fromSlot}-${variable.toSlot}${dataLine}`; + dotString += ` | ${(0, formatters_1.shortBytes32)(variable.offset)}${dataLine}` + } else if (variable.fromSlot === variable.toSlot) { + dotString += ` | ${variable.fromSlot}${dataLine}` + } else { + dotString += ` | ${variable.fromSlot}-${variable.toSlot}${dataLine}` } - }); + }) // write slot values if available if (options.data && !options.hideValues) { - dotString += `} | {value${dataLine}`; + dotString += `} | {value${dataLine}` startingVariables.forEach((variable, i) => { if (displayData[i]) { - dotString += ` | ${variable.slotValue || ''}${linePad}`; + dotString += ` | ${variable.slotValue || ''}${linePad}` + } else { + dotString += ' | ' } - else { - dotString += ` | `; - } - }); + }) } - const contractVariablePrefix = storageSection.type === converterClasses2Storage_1.StorageSectionType.Contract - ? '\\.' - : ''; - const dataLine2 = options.data ? `\\ndecoded data` : ''; - dotString += `} | { type: ${contractVariablePrefix}variable (bytes)${dataLine2}`; + const contractVariablePrefix = + storageSection.type === + converterClasses2Storage_1.StorageSectionType.Contract + ? '\\.' + : '' + const dataLine2 = options.data ? '\\ndecoded data' : '' + dotString += `} | { type: ${contractVariablePrefix}variable (bytes)${dataLine2}` // For each slot startingVariables.forEach((variable) => { // Get all the storage variables in this slot - const slotVariables = storageSection.variables.filter((s) => (!s.offset && s.fromSlot === variable.fromSlot) || - (s.offset && s.offset === variable.offset)); - const usedBytes = slotVariables.reduce((acc, s) => acc + s.byteSize, 0); + const slotVariables = storageSection.variables.filter( + (s) => + (!s.offset && s.fromSlot === variable.fromSlot) || + (s.offset && s.offset === variable.offset), + ) + const usedBytes = slotVariables.reduce((acc, s) => acc + s.byteSize, 0) if (usedBytes < 32) { // Create an unallocated variable for display purposes slotVariables.push({ @@ -95,36 +104,37 @@ function convertStorage2Dot(storageSection, dotString, options) { getValue: false, contractName: variable.contractName, name: '', - }); + }) } - const slotVariablesReversed = slotVariables.reverse(); + const slotVariablesReversed = slotVariables.reverse() // For each variable in the slot slotVariablesReversed.forEach((variable, i) => { if (i === 0) { - dotString += ` | { ${dotVariable(variable, storageSection.name)} `; - } - else { - dotString += ` | ${dotVariable(variable, storageSection.name)} `; + dotString += ` | { ${dotVariable(variable, storageSection.name)} ` + } else { + dotString += ` | ${dotVariable(variable, storageSection.name)} ` } - }); - dotString += '}'; - }); + }) + dotString += '}' + }) // Need to close off the last label - dotString += '}}"]\n'; - return dotString; + dotString += '}}"]\n' + return dotString } -exports.convertStorage2Dot = convertStorage2Dot; +exports.convertStorage2Dot = convertStorage2Dot const dotVariable = (variable, contractName) => { - const port = variable.referenceSectionId !== undefined ? `<${variable.id}>` : ''; - const contractNamePrefix = variable.contractName !== contractName - ? `${variable.contractName}.` - : ''; + const port = + variable.referenceSectionId !== undefined ? `<${variable.id}>` : '' + const contractNamePrefix = + variable.contractName !== contractName + ? `${variable.contractName}.` + : '' const variableValue = variable.parsedValue ? `\\n\\ ${variable.parsedValue}` - : ''; + : '' const variableName = variable.name ? `: ${contractNamePrefix}${variable.name}` - : ''; - return `${port} ${variable.type}${variableName} (${variable.byteSize})${variableValue}`; -}; -//# sourceMappingURL=converterStorage2Dot.js.map \ No newline at end of file + : '' + return `${port} ${variable.type}${variableName} (${variable.byteSize})${variableValue}` +} +// # sourceMappingURL=converterStorage2Dot.js.map diff --git a/lib/diff.d.ts b/lib/diff.d.ts index 077c9eb9..714b397b 100644 --- a/lib/diff.d.ts +++ b/lib/diff.d.ts @@ -4,4 +4,8 @@ * @param codeB * @param lineBuff the number of lines to display before and after each change. */ -export declare const diffCode: (codeA: string, codeB: string, lineBuff: number) => void; +export declare const diffCode: ( + codeA: string, + codeB: string, + lineBuff: number, +) => void diff --git a/lib/diff.js b/lib/diff.js index 43a3231b..5cc0b7c3 100644 --- a/lib/diff.js +++ b/lib/diff.js @@ -1,32 +1,64 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; +'use strict' +const __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k + let desc = Object.getOwnPropertyDescriptor(m, k) + if ( + !desc || + ('get' in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k] + }, + } + } + Object.defineProperty(o, k2, desc) + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k + o[k2] = m[k] + }) +const __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { + enumerable: true, + value: v, + }) + } + : function (o, v) { + o.default = v + }) +const __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod + const result = {} + if (mod != null) { + for (const k in mod) { + if ( + k !== 'default' && + Object.prototype.hasOwnProperty.call(mod, k) + ) { + __createBinding(result, mod, k) + } + } + } + __setModuleDefault(result, mod) + return result } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.diffCode = void 0; -const diff_match_patch_1 = __importStar(require("diff-match-patch")); -const clc = require('cli-color'); -const SkippedLinesMarker = `\n---`; +Object.defineProperty(exports, '__esModule', { value: true }) +exports.diffCode = void 0 +const diff_match_patch_1 = __importStar(require('diff-match-patch')) +const clc = require('cli-color') +const SkippedLinesMarker = '\n---' /** * Compares code using Google's diff_match_patch and displays the results in the console. * @param codeA @@ -35,13 +67,13 @@ const SkippedLinesMarker = `\n---`; */ const diffCode = (codeA, codeB, lineBuff) => { // @ts-ignore - const dmp = new diff_match_patch_1.default(); - const diff = dmp.diff_main(codeA, codeB); - dmp.diff_cleanupSemantic(diff); - const linesB = countLines(codeB) + 1; - diff_pretty(diff, linesB, lineBuff); -}; -exports.diffCode = diffCode; + const dmp = new diff_match_patch_1.default() + const diff = dmp.diff_main(codeA, codeB) + dmp.diff_cleanupSemantic(diff) + const linesB = countLines(codeB) + 1 + diff_pretty(diff, linesB, lineBuff) +} +exports.diffCode = diffCode /** * Convert a diff array into human readable for the console * @param {!Array.} diffs Array of diff tuples. @@ -49,70 +81,81 @@ exports.diffCode = diffCode; * @param lineBuff number of a lines to output before and after the change */ const diff_pretty = (diffs, lines, lineBuff = 2) => { - const linePad = lines.toString().length; - let output = ''; - let diffIndex = 0; - let lineCount = 1; - const firstLineNumber = '1'.padStart(linePad) + ' '; + const linePad = lines.toString().length + let output = '' + let diffIndex = 0 + let lineCount = 1 + const firstLineNumber = '1'.padStart(linePad) + ' ' for (const diff of diffs) { - diffIndex++; - const initialLineNumber = diffIndex <= 1 ? firstLineNumber : ''; - const op = diff[0]; // Operation (insert, delete, equal) - const text = diff[1]; // Text of change. + diffIndex++ + const initialLineNumber = diffIndex <= 1 ? firstLineNumber : '' + const op = diff[0] // Operation (insert, delete, equal) + const text = diff[1] // Text of change. switch (op) { case diff_match_patch_1.DIFF_INSERT: // If first diff then we need to add the first line number - const linesInserted = addLineNumbers(text, lineCount, linePad); - output += initialLineNumber + clc.green(linesInserted); - lineCount += countLines(text); - break; + const linesInserted = addLineNumbers(text, lineCount, linePad) + output += initialLineNumber + clc.green(linesInserted) + lineCount += countLines(text) + break case diff_match_patch_1.DIFF_DELETE: // zero start line means blank line numbers are used - const linesDeleted = addLineNumbers(text, 0, linePad); - output += initialLineNumber + clc.red(linesDeleted); - break; + const linesDeleted = addLineNumbers(text, 0, linePad) + output += initialLineNumber + clc.red(linesDeleted) + break case diff_match_patch_1.DIFF_EQUAL: - const eolPositions = findEOLPositions(text); + const eolPositions = findEOLPositions(text) // If no changes yet if (diffIndex <= 1) { - output += lastLines(text, eolPositions, lineBuff, linePad); + output += lastLines(text, eolPositions, lineBuff, linePad) } // if no more changes else if (diffIndex === diffs.length) { - output += firstLines(text, eolPositions, lineBuff, lineCount, linePad); - } - else { + output += firstLines( + text, + eolPositions, + lineBuff, + lineCount, + linePad, + ) + } else { // else the first n lines and last n lines - output += firstAndLastLines(text, eolPositions, lineBuff, lineCount, linePad); + output += firstAndLastLines( + text, + eolPositions, + lineBuff, + lineCount, + linePad, + ) } - lineCount += eolPositions.length; - break; + lineCount += eolPositions.length + break } } - output += '\n'; - console.log(output); -}; + output += '\n' + console.log(output) +} /** * Used when there is no more changes left */ const firstLines = (text, eolPositions, lineBuff, lineStart, linePad) => { - const lines = text.slice(0, eolPositions[lineBuff]); - return addLineNumbers(lines, lineStart, linePad); -}; + const lines = text.slice(0, eolPositions[lineBuff]) + return addLineNumbers(lines, lineStart, linePad) +} /** * Used before the first change */ const lastLines = (text, eolPositions, lineBuff, linePad) => { - const eolFrom = eolPositions.length - (lineBuff + 1); - let lines = text; - let lineCount = 1; + const eolFrom = eolPositions.length - (lineBuff + 1) + let lines = text + let lineCount = 1 if (eolFrom >= 0) { - lines = eolFrom >= 0 ? text.slice(eolPositions[eolFrom] + 1) : text; - lineCount = eolFrom + 2; + lines = eolFrom >= 0 ? text.slice(eolPositions[eolFrom] + 1) : text + lineCount = eolFrom + 2 } - const firstLineNumber = lineCount.toString().padStart(linePad) + ' '; - return firstLineNumber + addLineNumbers(lines, lineCount, linePad); -}; + const firstLineNumber = lineCount.toString().padStart(linePad) + ' ' + return firstLineNumber + addLineNumbers(lines, lineCount, linePad) +} /** * Used between changes to show the lines after the last change and before the next change. * @param text @@ -121,44 +164,50 @@ const lastLines = (text, eolPositions, lineBuff, linePad) => { * @param lineStart * @param linePad */ -const firstAndLastLines = (text, eolPositions, lineBuff, lineStart, linePad) => { +const firstAndLastLines = ( + text, + eolPositions, + lineBuff, + lineStart, + linePad, +) => { if (eolPositions.length <= 2 * lineBuff) { - return addLineNumbers(text, lineStart, linePad); + return addLineNumbers(text, lineStart, linePad) } - const endFirstLines = eolPositions[lineBuff]; - const eolFrom = eolPositions.length - (lineBuff + 1); - const startLastLines = eolPositions[eolFrom]; + const endFirstLines = eolPositions[lineBuff] + const eolFrom = eolPositions.length - (lineBuff + 1) + const startLastLines = eolPositions[eolFrom] if (startLastLines <= endFirstLines) { - return addLineNumbers(text, lineStart, linePad); + return addLineNumbers(text, lineStart, linePad) } // Lines after the previous change - let lines = text.slice(0, endFirstLines); - let output = addLineNumbers(lines, lineStart, linePad); - output += SkippedLinesMarker; + let lines = text.slice(0, endFirstLines) + let output = addLineNumbers(lines, lineStart, linePad) + output += SkippedLinesMarker // Lines before the next change - lines = text.slice(startLastLines); - const lineCount = lineStart + eolFrom; - output += addLineNumbers(lines, lineCount, linePad); - return output; -}; + lines = text.slice(startLastLines) + const lineCount = lineStart + eolFrom + output += addLineNumbers(lines, lineCount, linePad) + return output +} /** * Gets the positions of the end of lines in the string * @param text */ const findEOLPositions = (text) => { - const eolPositions = []; + const eolPositions = [] text.split('').forEach((c, i) => { if (c === '\n') { - eolPositions.push(i); + eolPositions.push(i) } - }); - return eolPositions; -}; + }) + return eolPositions +} /** * Counts the number of carriage returns in a string * @param text */ -const countLines = (text) => (text.match(/\n/g) || '').length; +const countLines = (text) => (text.match(/\n/g) || '').length /** * Adds left padded line numbers to each line. * @param text with the lines of code @@ -166,23 +215,21 @@ const countLines = (text) => (text.match(/\n/g) || '').length; * @param linePad the width of the largest number which may not be in the text */ const addLineNumbers = (text, lineStart, linePad) => { - let lineCount = lineStart; - let textWithLineNumbers = ''; + let lineCount = lineStart + let textWithLineNumbers = '' text.split('').forEach((c, i) => { if (c === '\n') { if (lineStart > 0) { textWithLineNumbers += `\n${(++lineCount) .toString() - .padStart(linePad)} `; - } - else { - textWithLineNumbers += `\n${' '.repeat(linePad)} `; + .padStart(linePad)} ` + } else { + textWithLineNumbers += `\n${' '.repeat(linePad)} ` } + } else { + textWithLineNumbers += c } - else { - textWithLineNumbers += c; - } - }); - return textWithLineNumbers; -}; -//# sourceMappingURL=diff.js.map \ No newline at end of file + }) + return textWithLineNumbers +} +// # sourceMappingURL=diff.js.map diff --git a/lib/diffContracts.d.ts b/lib/diffContracts.d.ts index 7812d1e0..eed9b686 100644 --- a/lib/diffContracts.d.ts +++ b/lib/diffContracts.d.ts @@ -1,36 +1,67 @@ -import { EtherscanParser } from './parserEtherscan'; +import { EtherscanParser } from './parserEtherscan' interface DiffOptions { - network: string; - bNetwork?: string; - lineBuffer: number; - summary?: boolean; - aFile?: string; - bFile?: string; - saveFiles?: boolean; + network: string + bNetwork?: string + lineBuffer: number + summary?: boolean + aFile?: string + bFile?: string + saveFiles?: boolean } interface DiffFiles { - filename?: string; - aCode?: string; - bCode?: string; - result: 'added' | 'removed' | 'match' | 'changed'; + filename?: string + aCode?: string + bCode?: string + result: 'added' | 'removed' | 'match' | 'changed' } interface CompareContracts { - files: DiffFiles[]; - contractNameA: string; - contractNameB?: string; - local?: 'file' | 'folders'; + files: DiffFiles[] + contractNameA: string + contractNameB?: string + local?: 'file' | 'folders' } -export declare const compareVerifiedContracts: (addressA: string, aEtherscanParser: EtherscanParser, addressB: string, bEtherscanParser: EtherscanParser, options: DiffOptions) => Promise; -export declare const compareVerified2Local: (addressA: string, aEtherscanParser: EtherscanParser, fileOrBaseFolders: string[], options: DiffOptions) => Promise; -export declare const compareFlattenContracts: (addressA: string, addressB: string, aEtherscanParser: EtherscanParser, bEtherscanParser: EtherscanParser, options: DiffOptions) => Promise<{ - contractNameA: string; - contractNameB: string; -}>; -export declare const diffVerified2Local: (addressA: string, etherscanParserA: EtherscanParser, fileOrBaseFolders: string[], ignoreFilesOrFolders?: string[]) => Promise; -export declare const diffVerifiedContracts: (addressA: string, addressB: string, etherscanParserA: EtherscanParser, etherscanParserB: EtherscanParser, options: DiffOptions) => Promise; -export declare const displayFileDiffSummary: (fileDiffs: DiffFiles[]) => void; -export declare const displayFileDiffs: (fileDiffs: DiffFiles[], options?: { - lineBuffer?: number; - aFile?: string; -}) => void; -export {}; +export declare const compareVerifiedContracts: ( + addressA: string, + aEtherscanParser: EtherscanParser, + addressB: string, + bEtherscanParser: EtherscanParser, + options: DiffOptions, +) => Promise +export declare const compareVerified2Local: ( + addressA: string, + aEtherscanParser: EtherscanParser, + fileOrBaseFolders: string[], + options: DiffOptions, +) => Promise +export declare const compareFlattenContracts: ( + addressA: string, + addressB: string, + aEtherscanParser: EtherscanParser, + bEtherscanParser: EtherscanParser, + options: DiffOptions, +) => Promise<{ + contractNameA: string + contractNameB: string +}> +export declare const diffVerified2Local: ( + addressA: string, + etherscanParserA: EtherscanParser, + fileOrBaseFolders: string[], + ignoreFilesOrFolders?: string[], +) => Promise +export declare const diffVerifiedContracts: ( + addressA: string, + addressB: string, + etherscanParserA: EtherscanParser, + etherscanParserB: EtherscanParser, + options: DiffOptions, +) => Promise +export declare const displayFileDiffSummary: (fileDiffs: DiffFiles[]) => void +export declare const displayFileDiffs: ( + fileDiffs: DiffFiles[], + options?: { + lineBuffer?: number + aFile?: string + }, +) => void +export {} diff --git a/lib/diffContracts.js b/lib/diffContracts.js index ec81ae7c..1c5a4292 100644 --- a/lib/diffContracts.js +++ b/lib/diffContracts.js @@ -1,108 +1,174 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.displayFileDiffs = exports.displayFileDiffSummary = exports.diffVerifiedContracts = exports.diffVerified2Local = exports.compareFlattenContracts = exports.compareVerified2Local = exports.compareVerifiedContracts = void 0; -const clc = require('cli-color'); -const path_1 = require("path"); -const parserFiles_1 = require("./parserFiles"); -const writerFiles_1 = require("./writerFiles"); -const regEx_1 = require("./utils/regEx"); -const diff_1 = require("./utils/diff"); -const debug = require('debug')('sol2uml'); -const compareVerifiedContracts = async (addressA, aEtherscanParser, addressB, bEtherscanParser, options) => { - const { contractNameA, contractNameB, files } = await (0, exports.diffVerifiedContracts)(addressA, addressB, aEtherscanParser, bEtherscanParser, options); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.displayFileDiffs = + exports.displayFileDiffSummary = + exports.diffVerifiedContracts = + exports.diffVerified2Local = + exports.compareFlattenContracts = + exports.compareVerified2Local = + exports.compareVerifiedContracts = + void 0 +const clc = require('cli-color') +const path_1 = require('path') +const parserFiles_1 = require('./parserFiles') +const writerFiles_1 = require('./writerFiles') +const regEx_1 = require('./utils/regEx') +const diff_1 = require('./utils/diff') +const debug = require('debug')('sol2uml') +const compareVerifiedContracts = async ( + addressA, + aEtherscanParser, + addressB, + bEtherscanParser, + options, +) => { + const { contractNameA, contractNameB, files } = await (0, + exports.diffVerifiedContracts)( + addressA, + addressB, + aEtherscanParser, + bEtherscanParser, + options, + ) if (!options.summary) { - (0, exports.displayFileDiffs)(files, options); + ;(0, exports.displayFileDiffs)(files, options) } - const aFileDesc = options.aFile ? `"${options.aFile}" file for the ` : ''; + const aFileDesc = options.aFile ? `"${options.aFile}" file for the ` : '' const bFileDesc = options.aFile ? `"${options.bFile || options.aFile}" file for the ` - : ''; - console.log(`Compared the ${aFileDesc}"${contractNameA}" contract with address ${addressA} on ${options.network}`); - console.log(`to the ${bFileDesc}"${contractNameB}" contract with address ${addressB} on ${options.bNetwork || options.network}\n`); - (0, exports.displayFileDiffSummary)(files); -}; -exports.compareVerifiedContracts = compareVerifiedContracts; -const compareVerified2Local = async (addressA, aEtherscanParser, fileOrBaseFolders, options) => { + : '' + console.log( + `Compared the ${aFileDesc}"${contractNameA}" contract with address ${addressA} on ${options.network}`, + ) + console.log( + `to the ${bFileDesc}"${contractNameB}" contract with address ${addressB} on ${options.bNetwork || options.network}\n`, + ) + ;(0, exports.displayFileDiffSummary)(files) +} +exports.compareVerifiedContracts = compareVerifiedContracts +const compareVerified2Local = async ( + addressA, + aEtherscanParser, + fileOrBaseFolders, + options, +) => { // compare verified contract to local files - const { contractNameA, files, local } = await (0, exports.diffVerified2Local)(addressA, aEtherscanParser, fileOrBaseFolders); + const { contractNameA, files, local } = await (0, + exports.diffVerified2Local)(addressA, aEtherscanParser, fileOrBaseFolders) if (!options.summary) { - (0, exports.displayFileDiffs)(files, options); + ;(0, exports.displayFileDiffs)(files, options) } - const aFileDesc = options.aFile ? `"${options.aFile}" file with the ` : ''; - console.log(`Compared the ${aFileDesc}"${contractNameA}" contract with address ${addressA} on ${options.network}`); + const aFileDesc = options.aFile ? `"${options.aFile}" file with the ` : '' + console.log( + `Compared the ${aFileDesc}"${contractNameA}" contract with address ${addressA} on ${options.network}`, + ) if (local) { - console.log(`to local file "${fileOrBaseFolders}"\n`); + console.log(`to local file "${fileOrBaseFolders}"\n`) + } else { + console.log(`to local files under folders "${fileOrBaseFolders}"\n`) } - else { - console.log(`to local files under folders "${fileOrBaseFolders}"\n`); - } - (0, exports.displayFileDiffSummary)(files); -}; -exports.compareVerified2Local = compareVerified2Local; -const compareFlattenContracts = async (addressA, addressB, aEtherscanParser, bEtherscanParser, options) => { + ;(0, exports.displayFileDiffSummary)(files) +} +exports.compareVerified2Local = compareVerified2Local +const compareFlattenContracts = async ( + addressA, + addressB, + aEtherscanParser, + bEtherscanParser, + options, +) => { // Get verified Solidity code from Etherscan and flatten - const { solidityCode: codeA, contractName: contractNameA } = await aEtherscanParser.getSolidityCode(addressA, options.aFile); - const { solidityCode: codeB, contractName: contractNameB } = await bEtherscanParser.getSolidityCode(addressB, options.bFile || options.aFile); - (0, diff_1.diffCode)(codeA, codeB, options.lineBuffer); + const { solidityCode: codeA, contractName: contractNameA } = + await aEtherscanParser.getSolidityCode(addressA, options.aFile) + const { solidityCode: codeB, contractName: contractNameB } = + await bEtherscanParser.getSolidityCode( + addressB, + options.bFile || options.aFile, + ) + ;(0, diff_1.diffCode)(codeA, codeB, options.lineBuffer) if (options.saveFiles) { - await (0, writerFiles_1.writeSourceCode)(codeA, addressA); - await (0, writerFiles_1.writeSourceCode)(codeB, addressB); + await (0, writerFiles_1.writeSourceCode)(codeA, addressA) + await (0, writerFiles_1.writeSourceCode)(codeB, addressB) } if (options.bFile || options.aFile) { - console.log(`Compared the "${options.aFile}" file with the "${contractNameA}" contract with address ${addressA} on ${options.network}`); - console.log(`to the "${options.bFile || options.aFile}" file for the "${contractNameB}" contract with address ${addressB} on ${options.bNetwork || options.network}\n`); - } - else { - console.log(`Compared the flattened "${contractNameA}" contract with address ${addressA} on ${options.network}`); - console.log(`to the flattened "${contractNameB}" contract with address ${addressB} on ${options.bNetwork || options.network}\n`); + console.log( + `Compared the "${options.aFile}" file with the "${contractNameA}" contract with address ${addressA} on ${options.network}`, + ) + console.log( + `to the "${options.bFile || options.aFile}" file for the "${contractNameB}" contract with address ${addressB} on ${options.bNetwork || options.network}\n`, + ) + } else { + console.log( + `Compared the flattened "${contractNameA}" contract with address ${addressA} on ${options.network}`, + ) + console.log( + `to the flattened "${contractNameB}" contract with address ${addressB} on ${options.bNetwork || options.network}\n`, + ) } - return { contractNameA, contractNameB }; -}; -exports.compareFlattenContracts = compareFlattenContracts; -const diffVerified2Local = async (addressA, etherscanParserA, fileOrBaseFolders, ignoreFilesOrFolders = []) => { - const files = []; + return { contractNameA, contractNameB } +} +exports.compareFlattenContracts = compareFlattenContracts +const diffVerified2Local = async ( + addressA, + etherscanParserA, + fileOrBaseFolders, + ignoreFilesOrFolders = [], +) => { + const files = [] // Get all the source files for the verified contract from Etherscan - const { files: aFiles, contractName: contractNameA } = await etherscanParserA.getSourceCode(addressA); + const { files: aFiles, contractName: contractNameA } = + await etherscanParserA.getSourceCode(addressA) if (aFiles.length === 1 && (0, regEx_1.isAddress)(aFiles[0].filename)) { // The verified contract is a single, flat file - const aFile = aFiles[0]; - const bFile = fileOrBaseFolders[0]; + const aFile = aFiles[0] + const bFile = fileOrBaseFolders[0] if ((0, parserFiles_1.isFolder)(bFile)) { - throw Error(`Contract with address ${addressA} is a single, flat file so cannot be compared to a local files under folder(s) "${fileOrBaseFolders.toString()}".`); + throw Error( + `Contract with address ${addressA} is a single, flat file so cannot be compared to a local files under folder(s) "${fileOrBaseFolders.toString()}".`, + ) } // Try and read the bFile - const bCode = (0, parserFiles_1.readFile)(bFile, 'sol'); + const bCode = (0, parserFiles_1.readFile)(bFile, 'sol') files.push({ filename: aFile.filename, aCode: aFile.code, bCode, result: aFile.code === bCode ? 'match' : 'changed', - }); + }) return { files, contractNameA, local: 'file', - }; + } } - const bFiles = await (0, parserFiles_1.getSolidityFilesFromFolderOrFiles)(fileOrBaseFolders, ignoreFilesOrFolders); + const bFiles = await (0, parserFiles_1.getSolidityFilesFromFolderOrFiles)( + fileOrBaseFolders, + ignoreFilesOrFolders, + ) // For each file in the A contract for (const aFile of aFiles) { // Look for A contract filename in local filesystem - let bFile; + let bFile // for each of the base folders for (const baseFolder of fileOrBaseFolders) { bFile = bFiles.find((bFile) => { - const resolvedPath = (0, path_1.resolve)(process.cwd(), baseFolder, aFile.filename); - return bFile === resolvedPath; - }); + const resolvedPath = (0, path_1.resolve)( + process.cwd(), + baseFolder, + aFile.filename, + ) + return bFile === resolvedPath + }) if (bFile) { - break; + break } } if (bFile) { - debug(`Matched verified file ${aFile.filename} to local file ${bFile}`); + debug( + `Matched verified file ${aFile.filename} to local file ${bFile}`, + ) // Try and read code from bFile - const bCode = (0, parserFiles_1.readFile)(bFile); + const bCode = (0, parserFiles_1.readFile)(bFile) // The A contract filename exists in the B contract if (aFile.code !== bCode) { // console.log(`${aFile.filename} ${clc.red('different')}:`) @@ -111,56 +177,65 @@ const diffVerified2Local = async (addressA, etherscanParserA, fileOrBaseFolders, aCode: aFile.code, bCode, result: 'changed', - }); - } - else { + }) + } else { files.push({ filename: aFile.filename, aCode: aFile.code, bCode, result: 'match', - }); + }) } - } - else { - debug(`Failed to find local file for verified files ${aFile.filename}`); + } else { + debug( + `Failed to find local file for verified files ${aFile.filename}`, + ) // The A contract filename does not exist in the B contract files.push({ filename: aFile.filename, aCode: aFile.code, result: 'removed', - }); + }) } } // Sort by filename return { files: files.sort((a, b) => a.filename.localeCompare(b.filename)), contractNameA, - }; -}; -exports.diffVerified2Local = diffVerified2Local; -const diffVerifiedContracts = async (addressA, addressB, etherscanParserA, etherscanParserB, options) => { - const files = []; - const { files: aFiles, contractName: contractNameA } = await etherscanParserA.getSourceCode(addressA); - const { files: bFiles, contractName: contractNameB } = await etherscanParserB.getSourceCode(addressB); + } +} +exports.diffVerified2Local = diffVerified2Local +const diffVerifiedContracts = async ( + addressA, + addressB, + etherscanParserA, + etherscanParserB, + options, +) => { + const files = [] + const { files: aFiles, contractName: contractNameA } = + await etherscanParserA.getSourceCode(addressA) + const { files: bFiles, contractName: contractNameB } = + await etherscanParserB.getSourceCode(addressB) if (aFiles.length === 1 && bFiles.length === 1) { - if ((0, regEx_1.isAddress)(aFiles[0].filename)) + if ((0, regEx_1.isAddress)(aFiles[0].filename)) { files.push({ filename: `${aFiles[0].filename} to ${bFiles[0].filename}`, aCode: aFiles[0].code, bCode: bFiles[0].code, result: aFiles[0].code === bFiles[0].code ? 'match' : 'changed', - }); + }) + } return { files, contractNameA, contractNameB, - }; + } } // For each file in the A contract for (const aFile of aFiles) { // Look for A contract filename in B contract - const bFile = bFiles.find((bFile) => bFile.filename === aFile.filename); + const bFile = bFiles.find((bFile) => bFile.filename === aFile.filename) if (bFile) { // The A contract filename exists in the B contract if (aFile.code !== bFile.code) { @@ -170,37 +245,35 @@ const diffVerifiedContracts = async (addressA, addressB, etherscanParserA, ether aCode: aFile.code, bCode: bFile.code, result: 'changed', - }); - } - else { + }) + } else { files.push({ filename: aFile.filename, aCode: aFile.code, bCode: bFile.code, result: 'match', - }); + }) } - } - else { + } else { // The A contract filename does not exist in the B contract files.push({ filename: aFile.filename, aCode: aFile.code, result: 'removed', - }); + }) } } // For each file in the B contract for (const bFile of bFiles) { // Look for B contract filename in A contract - const aFile = aFiles.find((aFile) => aFile.filename === bFile.filename); + const aFile = aFiles.find((aFile) => aFile.filename === bFile.filename) if (!aFile) { // The B contract filename does not exist in the A contract files.push({ filename: bFile.filename, bCode: bFile.code, result: 'added', - }); + }) } } // Sort by filename @@ -208,54 +281,63 @@ const diffVerifiedContracts = async (addressA, addressB, etherscanParserA, ether files: files.sort((a, b) => a.filename.localeCompare(b.filename)), contractNameA, contractNameB, - }; -}; -exports.diffVerifiedContracts = diffVerifiedContracts; + } +} +exports.diffVerifiedContracts = diffVerifiedContracts const displayFileDiffSummary = (fileDiffs) => { for (const file of fileDiffs) { switch (file.result) { case 'match': - console.log(`${file.result.padEnd(7)} ${file.filename}`); - break; + console.log(`${file.result.padEnd(7)} ${file.filename}`) + break case 'added': - console.log(`${clc.green(file.result.padEnd(7))} ${file.filename}`); - break; + console.log( + `${clc.green(file.result.padEnd(7))} ${file.filename}`, + ) + break case 'changed': case 'removed': - console.log(`${clc.red(file.result)} ${file.filename}`); - break; + console.log(`${clc.red(file.result)} ${file.filename}`) + break } } -}; -exports.displayFileDiffSummary = displayFileDiffSummary; +} +exports.displayFileDiffSummary = displayFileDiffSummary const displayFileDiffs = (fileDiffs, options = {}) => { - let aFileFound = false; + let aFileFound = false for (const file of fileDiffs) { if (options.aFile) { - if (file.filename !== options.aFile) - continue; - else - aFileFound = true; + if (file.filename !== options.aFile) { + continue + } else { + aFileFound = true + } } switch (file.result) { case 'added': - console.log(`Added ${file.filename}`); - console.log(clc.green(file.bCode)); - break; + console.log(`Added ${file.filename}`) + console.log(clc.green(file.bCode)) + break case 'changed': - console.log(`Changed ${file.filename}`); - (0, diff_1.diffCode)(file.aCode, file.bCode, options.lineBuffer); - break; + console.log(`Changed ${file.filename}`) + ;(0, diff_1.diffCode)( + file.aCode, + file.bCode, + options.lineBuffer, + ) + break case 'removed': - console.log(`Removed ${file.filename}`); - console.log(clc.red(file.aCode)); - break; + console.log(`Removed ${file.filename}`) + console.log(clc.red(file.aCode)) + break } } // If filtering on an aFile, but it was not found if (options.aFile && !aFileFound) { - throw new Error(`Could not display code diff for file "${options.aFile}".\nMake sure the full file path and extension is used as displayed in the file summary.`); + throw new Error( + `Could not display code diff for file "${options.aFile}".\nMake sure the full file path and extension is used as displayed in the file summary.`, + ) } -}; -exports.displayFileDiffs = displayFileDiffs; -//# sourceMappingURL=diffContracts.js.map \ No newline at end of file +} +exports.displayFileDiffs = displayFileDiffs +// # sourceMappingURL=diffContracts.js.map diff --git a/lib/filterClasses.d.ts b/lib/filterClasses.d.ts index 27c94cfc..b588d5d1 100644 --- a/lib/filterClasses.d.ts +++ b/lib/filterClasses.d.ts @@ -1,13 +1,16 @@ -import { WeightedDiGraph } from 'js-graph-algorithms'; -import { UmlClass } from './umlClass'; -import { ClassOptions } from './converterClass2Dot'; +import { WeightedDiGraph } from 'js-graph-algorithms' +import { UmlClass } from './umlClass' +import { ClassOptions } from './converterClass2Dot' /** * Filter out any UML Class types that are to be hidden. * @param umlClasses array of UML classes of type `UMLClass` * @param options sol2uml class options * @return umlClasses filtered list of UML classes of type `UMLClass` */ -export declare const filterHiddenClasses: (umlClasses: readonly UmlClass[], options: ClassOptions) => UmlClass[]; +export declare const filterHiddenClasses: ( + umlClasses: readonly UmlClass[], + options: ClassOptions, +) => UmlClass[] /** * Finds all the UML classes that have an association with a list of base contract names. * The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants. @@ -16,7 +19,11 @@ export declare const filterHiddenClasses: (umlClasses: readonly UmlClass[], opti * @param depth limit the number of associations from the base contract. * @return filteredUmlClasses list of UML classes of type `UMLClass` */ -export declare const classesConnectedToBaseContracts: (umlClasses: readonly UmlClass[], baseContractNames: readonly string[], depth?: number) => UmlClass[]; +export declare const classesConnectedToBaseContracts: ( + umlClasses: readonly UmlClass[], + baseContractNames: readonly string[], + depth?: number, +) => UmlClass[] /** * Finds all the UML classes that have an association with a base contract name. * The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants. @@ -26,7 +33,14 @@ export declare const classesConnectedToBaseContracts: (umlClasses: readonly UmlC * @param depth limit the number of associations from the base contract. * @return filteredUmlClasses list of UML classes of type `UMLClass` */ -export declare const classesConnectedToBaseContract: (umlClasses: readonly UmlClass[], baseContractName: string, weightedDirectedGraph: WeightedDiGraph, depth?: number) => { - [contractName: string]: UmlClass; -}; -export declare const topologicalSortClasses: (umlClasses: readonly UmlClass[]) => UmlClass[]; +export declare const classesConnectedToBaseContract: ( + umlClasses: readonly UmlClass[], + baseContractName: string, + weightedDirectedGraph: WeightedDiGraph, + depth?: number, +) => { + [contractName: string]: UmlClass +} +export declare const topologicalSortClasses: ( + umlClasses: readonly UmlClass[], +) => UmlClass[] diff --git a/lib/filterClasses.js b/lib/filterClasses.js index 70d9a77e..d5bc4e58 100644 --- a/lib/filterClasses.js +++ b/lib/filterClasses.js @@ -1,10 +1,14 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.topologicalSortClasses = exports.classesConnectedToBaseContract = exports.classesConnectedToBaseContracts = exports.filterHiddenClasses = void 0; -const js_graph_algorithms_1 = require("js-graph-algorithms"); -const umlClass_1 = require("./umlClass"); -const associations_1 = require("./associations"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.topologicalSortClasses = + exports.classesConnectedToBaseContract = + exports.classesConnectedToBaseContracts = + exports.filterHiddenClasses = + void 0 +const js_graph_algorithms_1 = require('js-graph-algorithms') +const umlClass_1 = require('./umlClass') +const associations_1 = require('./associations') +const debug = require('debug')('sol2uml') /** * Filter out any UML Class types that are to be hidden. * @param umlClasses array of UML classes of type `UMLClass` @@ -12,21 +16,26 @@ const debug = require('debug')('sol2uml'); * @return umlClasses filtered list of UML classes of type `UMLClass` */ const filterHiddenClasses = (umlClasses, options) => { - return umlClasses.filter((u) => (u.stereotype === umlClass_1.ClassStereotype.Enum && !options.hideEnums) || - (u.stereotype === umlClass_1.ClassStereotype.Struct && !options.hideStructs) || - (u.stereotype === umlClass_1.ClassStereotype.Abstract && - !options.hideAbstracts) || - (u.stereotype === umlClass_1.ClassStereotype.Interface && - !options.hideInterfaces) || - (u.stereotype === umlClass_1.ClassStereotype.Constant && - !options.hideConstants) || - (u.stereotype === umlClass_1.ClassStereotype.Library && - !options.hideLibraries) || - ((u.stereotype === umlClass_1.ClassStereotype.None || - u.stereotype === umlClass_1.ClassStereotype.Contract) && - !options.hideContracts)); -}; -exports.filterHiddenClasses = filterHiddenClasses; + return umlClasses.filter( + (u) => + (u.stereotype === umlClass_1.ClassStereotype.Enum && + !options.hideEnums) || + (u.stereotype === umlClass_1.ClassStereotype.Struct && + !options.hideStructs) || + (u.stereotype === umlClass_1.ClassStereotype.Abstract && + !options.hideAbstracts) || + (u.stereotype === umlClass_1.ClassStereotype.Interface && + !options.hideInterfaces) || + (u.stereotype === umlClass_1.ClassStereotype.Constant && + !options.hideConstants) || + (u.stereotype === umlClass_1.ClassStereotype.Library && + !options.hideLibraries) || + ((u.stereotype === umlClass_1.ClassStereotype.None || + u.stereotype === umlClass_1.ClassStereotype.Contract) && + !options.hideContracts), + ) +} +exports.filterHiddenClasses = filterHiddenClasses /** * Finds all the UML classes that have an association with a list of base contract names. * The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants. @@ -35,18 +44,27 @@ exports.filterHiddenClasses = filterHiddenClasses; * @param depth limit the number of associations from the base contract. * @return filteredUmlClasses list of UML classes of type `UMLClass` */ -const classesConnectedToBaseContracts = (umlClasses, baseContractNames, depth) => { - let filteredUmlClasses = {}; - const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses); +const classesConnectedToBaseContracts = ( + umlClasses, + baseContractNames, + depth, +) => { + let filteredUmlClasses = {} + const weightedDirectedGraph = loadWeightedDirectedGraph(umlClasses) for (const baseContractName of baseContractNames) { filteredUmlClasses = { ...filteredUmlClasses, - ...(0, exports.classesConnectedToBaseContract)(umlClasses, baseContractName, weightedDirectedGraph, depth), - }; + ...(0, exports.classesConnectedToBaseContract)( + umlClasses, + baseContractName, + weightedDirectedGraph, + depth, + ), + } } - return Object.values(filteredUmlClasses); -}; -exports.classesConnectedToBaseContracts = classesConnectedToBaseContracts; + return Object.values(filteredUmlClasses) +} +exports.classesConnectedToBaseContracts = classesConnectedToBaseContracts /** * Finds all the UML classes that have an association with a base contract name. * The associated classes can be contracts, abstract contracts, interfaces, libraries, enums, structs or constants. @@ -56,67 +74,99 @@ exports.classesConnectedToBaseContracts = classesConnectedToBaseContracts; * @param depth limit the number of associations from the base contract. * @return filteredUmlClasses list of UML classes of type `UMLClass` */ -const classesConnectedToBaseContract = (umlClasses, baseContractName, weightedDirectedGraph, depth = 1000) => { +const classesConnectedToBaseContract = ( + umlClasses, + baseContractName, + weightedDirectedGraph, + depth = 1000, +) => { // Find the base UML Class from the base contract name const baseUmlClass = umlClasses.find(({ name }) => { - return name === baseContractName; - }); + return name === baseContractName + }) if (!baseUmlClass) { - throw Error(`Failed to find base contract with name "${baseContractName}"`); + throw Error( + `Failed to find base contract with name "${baseContractName}"`, + ) } - const dfs = new js_graph_algorithms_1.Dijkstra(weightedDirectedGraph, baseUmlClass.id); + const dfs = new js_graph_algorithms_1.Dijkstra( + weightedDirectedGraph, + baseUmlClass.id, + ) // Get all the UML Classes that are connected to the base contract - const filteredUmlClasses = {}; + const filteredUmlClasses = {} for (const umlClass of umlClasses) { if (dfs.distanceTo(umlClass.id) <= depth) { - filteredUmlClasses[umlClass.name] = umlClass; + filteredUmlClasses[umlClass.name] = umlClass } } - return filteredUmlClasses; -}; -exports.classesConnectedToBaseContract = classesConnectedToBaseContract; + return filteredUmlClasses +} +exports.classesConnectedToBaseContract = classesConnectedToBaseContract function loadWeightedDirectedGraph(umlClasses) { const weightedDirectedGraph = new js_graph_algorithms_1.WeightedDiGraph( - // the number vertices in the graph - umlClass_1.UmlClass.idCounter + 1); + // the number vertices in the graph + umlClass_1.UmlClass.idCounter + 1, + ) for (const sourceUmlClass of umlClasses) { for (const association of Object.values(sourceUmlClass.associations)) { // Find the first UML Class that matches the target class name - const targetUmlClass = (0, associations_1.findAssociatedClass)(association, sourceUmlClass, umlClasses); + const targetUmlClass = (0, associations_1.findAssociatedClass)( + association, + sourceUmlClass, + umlClasses, + ) if (!targetUmlClass) { - continue; + continue } - const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id); - debug(`isTarget ${!!isTarget}: Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}`); - weightedDirectedGraph.addEdge(new js_graph_algorithms_1.Edge(sourceUmlClass.id, targetUmlClass.id, 1)); + const isTarget = umlClasses.find((u) => u.id === targetUmlClass.id) + debug( + `isTarget ${!!isTarget}: Adding edge from ${sourceUmlClass.name} with id ${sourceUmlClass.id} to ${targetUmlClass.name} with id ${targetUmlClass.id} and type ${targetUmlClass.stereotype}`, + ) + weightedDirectedGraph.addEdge( + new js_graph_algorithms_1.Edge( + sourceUmlClass.id, + targetUmlClass.id, + 1, + ), + ) } } - return weightedDirectedGraph; + return weightedDirectedGraph } const topologicalSortClasses = (umlClasses) => { - const directedAcyclicGraph = loadDirectedAcyclicGraph(umlClasses); - const topologicalSort = new js_graph_algorithms_1.TopologicalSort(directedAcyclicGraph); + const directedAcyclicGraph = loadDirectedAcyclicGraph(umlClasses) + const topologicalSort = new js_graph_algorithms_1.TopologicalSort( + directedAcyclicGraph, + ) // Topological sort the class ids - const sortedUmlClassIds = topologicalSort.order().reverse(); - const sortedUmlClasses = sortedUmlClassIds.map((umlClassId) => - // Lookup the UmlClass for each class id - umlClasses.find((umlClass) => umlClass.id === umlClassId)); + const sortedUmlClassIds = topologicalSort.order().reverse() + const sortedUmlClasses = sortedUmlClassIds.map((umlClassId) => + // Lookup the UmlClass for each class id + umlClasses.find((umlClass) => umlClass.id === umlClassId), + ) // Filter out any unfound classes. This happens when diff sources the second contract. - return sortedUmlClasses.filter((umlClass) => umlClass !== undefined); -}; -exports.topologicalSortClasses = topologicalSortClasses; + return sortedUmlClasses.filter((umlClass) => umlClass !== undefined) +} +exports.topologicalSortClasses = topologicalSortClasses const loadDirectedAcyclicGraph = (umlClasses) => { - const directedAcyclicGraph = new js_graph_algorithms_1.DiGraph(umlClass_1.UmlClass.idCounter); // the number vertices in the graph + const directedAcyclicGraph = new js_graph_algorithms_1.DiGraph( + umlClass_1.UmlClass.idCounter, + ) // the number vertices in the graph for (const sourceUmlClass of umlClasses) { for (const association of Object.values(sourceUmlClass.associations)) { // Find the first UML Class that matches the target class name - const targetUmlClass = (0, associations_1.findAssociatedClass)(association, sourceUmlClass, umlClasses); + const targetUmlClass = (0, associations_1.findAssociatedClass)( + association, + sourceUmlClass, + umlClasses, + ) if (!targetUmlClass) { - continue; + continue } - directedAcyclicGraph.addEdge(sourceUmlClass.id, targetUmlClass.id); + directedAcyclicGraph.addEdge(sourceUmlClass.id, targetUmlClass.id) } } - return directedAcyclicGraph; -}; -//# sourceMappingURL=filterClasses.js.map \ No newline at end of file + return directedAcyclicGraph +} +// # sourceMappingURL=filterClasses.js.map diff --git a/lib/index.d.ts b/lib/index.d.ts index e2187bfc..d87c6b11 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -1,10 +1,10 @@ -export * from './converterAST2Classes'; -export * from './converterClass2Dot'; -export * from './converterClasses2Dot'; -export * from './converterClasses2Storage'; -export * from './parserEtherscan'; -export * from './parserFiles'; -export * from './parserGeneral'; -export * from './typeGuards'; -export * from './umlClass'; -export * from './writerFiles'; +export * from './converterAST2Classes' +export * from './converterClass2Dot' +export * from './converterClasses2Dot' +export * from './converterClasses2Storage' +export * from './parserEtherscan' +export * from './parserFiles' +export * from './parserGeneral' +export * from './typeGuards' +export * from './umlClass' +export * from './writerFiles' diff --git a/lib/index.js b/lib/index.js index d999803b..a118da08 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,27 +1,50 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; +'use strict' +const __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k + let desc = Object.getOwnPropertyDescriptor(m, k) + if ( + !desc || + ('get' in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k] + }, + } + } + Object.defineProperty(o, k2, desc) + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k + o[k2] = m[k] + }) +const __exportStar = + (this && this.__exportStar) || + function (m, exports) { + for (const p in m) { + if ( + p !== 'default' && + !Object.prototype.hasOwnProperty.call(exports, p) + ) { + __createBinding(exports, m, p) + } + } } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -__exportStar(require("./converterAST2Classes"), exports); -__exportStar(require("./converterClass2Dot"), exports); -__exportStar(require("./converterClasses2Dot"), exports); -__exportStar(require("./converterClasses2Storage"), exports); -__exportStar(require("./parserEtherscan"), exports); -__exportStar(require("./parserFiles"), exports); -__exportStar(require("./parserGeneral"), exports); -__exportStar(require("./typeGuards"), exports); -__exportStar(require("./umlClass"), exports); -__exportStar(require("./writerFiles"), exports); -//# sourceMappingURL=index.js.map \ No newline at end of file +Object.defineProperty(exports, '__esModule', { value: true }) +__exportStar(require('./converterAST2Classes'), exports) +__exportStar(require('./converterClass2Dot'), exports) +__exportStar(require('./converterClasses2Dot'), exports) +__exportStar(require('./converterClasses2Storage'), exports) +__exportStar(require('./parserEtherscan'), exports) +__exportStar(require('./parserFiles'), exports) +__exportStar(require('./parserGeneral'), exports) +__exportStar(require('./typeGuards'), exports) +__exportStar(require('./umlClass'), exports) +__exportStar(require('./writerFiles'), exports) +// # sourceMappingURL=index.js.map diff --git a/lib/parserEtherscan.d.ts b/lib/parserEtherscan.d.ts index 81cb72a0..e5085ef2 100644 --- a/lib/parserEtherscan.d.ts +++ b/lib/parserEtherscan.d.ts @@ -1,61 +1,84 @@ -import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types'; -import { UmlClass } from './umlClass'; +import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types' +import { UmlClass } from './umlClass' export interface Remapping { - from: RegExp; - to: string; + from: RegExp + to: string } -export declare const networks: readonly ["mainnet", "holesky", "sepolia", "polygon", "arbitrum", "avalanche", "bsc", "crono", "fantom", "moonbeam", "optimism", "gnosis", "celo", "scroll", "base", "sonic"]; -export type Network = (typeof networks)[number]; +export declare const networks: readonly [ + 'mainnet', + 'holesky', + 'sepolia', + 'polygon', + 'arbitrum', + 'avalanche', + 'bsc', + 'crono', + 'fantom', + 'moonbeam', + 'optimism', + 'gnosis', + 'celo', + 'scroll', + 'base', + 'sonic', +] +export type Network = (typeof networks)[number] export declare class EtherscanParser { - protected apikey: string; - network: Network; - readonly url: string; - constructor(apikey?: string, network?: Network, url?: string); + protected apikey: string + network: Network + readonly url: string + constructor(apikey?: string, network?: Network, url?: string) /** * Parses the verified source code files from Etherscan * @param contractAddress Ethereum contract address with a 0x prefix * @return Promise with an array of UmlClass objects */ getUmlClasses(contractAddress: string): Promise<{ - umlClasses: UmlClass[]; - contractName: string; - }>; + umlClasses: UmlClass[] + contractName: string + }> /** * Get Solidity code from Etherscan for a contract and merges all files * into one long string of Solidity code. * @param contractAddress Ethereum contract address with a 0x prefix * @return Promise string of Solidity code */ - getSolidityCode(contractAddress: string, filename?: string): Promise<{ - solidityCode: string; - contractName: string; - }>; + getSolidityCode( + contractAddress: string, + filename?: string, + ): Promise<{ + solidityCode: string + contractName: string + }> /** * Parses Solidity source code into an ASTNode object * @param sourceCode Solidity source code * @return Promise with an ASTNode object from @solidity-parser/parser */ - parseSourceCode(sourceCode: string): Promise; + parseSourceCode(sourceCode: string): Promise /** * Calls Etherscan to get the verified source code for the specified contract address * @param contractAddress Ethereum contract address with a 0x prefix * @oaram filename optional, case-sensitive name of the source file without the .sol */ - getSourceCode(contractAddress: string, filename?: string): Promise<{ + getSourceCode( + contractAddress: string, + filename?: string, + ): Promise<{ files: { - code: string; - filename: string; - }[]; - contractName: string; - compilerVersion: string; - remappings: Remapping[]; - }>; + code: string + filename: string + }[] + contractName: string + compilerVersion: string + remappings: Remapping[] + }> } /** * Parses Ethersan's remappings config in its API response * @param rawMappings */ -export declare const parseRemappings: (rawMappings: string[]) => Remapping[]; +export declare const parseRemappings: (rawMappings: string[]) => Remapping[] /** * Parses a single mapping. For example * "@openzeppelin/=lib/openzeppelin-contracts/" @@ -63,4 +86,4 @@ export declare const parseRemappings: (rawMappings: string[]) => Remapping[]; * https://etherscan.io/address/0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B#code * @param mapping */ -export declare const parseRemapping: (mapping: string) => Remapping; +export declare const parseRemapping: (mapping: string) => Remapping diff --git a/lib/parserEtherscan.js b/lib/parserEtherscan.js index e5b62e55..167e864c 100644 --- a/lib/parserEtherscan.js +++ b/lib/parserEtherscan.js @@ -1,17 +1,23 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.parseRemapping = exports.parseRemappings = exports.EtherscanParser = exports.networks = void 0; -const axios_1 = __importDefault(require("axios")); -const parser_1 = require("@solidity-parser/parser"); -const converterAST2Classes_1 = require("./converterAST2Classes"); -const filterClasses_1 = require("./filterClasses"); -const regEx_1 = require("./utils/regEx"); -const path_1 = __importDefault(require("path")); -require('axios-debug-log'); -const debug = require('debug')('sol2uml'); +'use strict' +const __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod } + } +Object.defineProperty(exports, '__esModule', { value: true }) +exports.parseRemapping = + exports.parseRemappings = + exports.EtherscanParser = + exports.networks = + void 0 +const axios_1 = __importDefault(require('axios')) +const parser_1 = require('@solidity-parser/parser') +const converterAST2Classes_1 = require('./converterAST2Classes') +const filterClasses_1 = require('./filterClasses') +const regEx_1 = require('./utils/regEx') +const path_1 = __importDefault(require('path')) +require('axios-debug-log') +const debug = require('debug')('sol2uml') exports.networks = [ 'mainnet', 'holesky', @@ -29,96 +35,94 @@ exports.networks = [ 'scroll', 'base', 'sonic', -]; +] class EtherscanParser { - constructor(apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', network = 'mainnet', url) { - this.apikey = apikey; - this.network = network; + constructor( + apikey = 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1', + network = 'mainnet', + url, + ) { + this.apikey = apikey + this.network = network if (url) { - this.url = url; - return; + this.url = url + return } if (!exports.networks.includes(network)) { - throw new Error(`Invalid network "${network}". Must be one of ${exports.networks}`); - } - else if (network === 'mainnet') { - this.url = 'https://api.etherscan.io/api'; - } - else if (network === 'polygon') { - this.url = 'https://api.polygonscan.com/api'; - this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB'; - } - else if (network === 'arbitrum') { - this.url = 'https://api.arbiscan.io/api'; - this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ'; - } - else if (network === 'avalanche') { - this.url = 'https://api.snowtrace.io/api'; - this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY'; - } - else if (network === 'bsc') { - this.url = 'https://api.bscscan.com/api'; - this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN'; - } - else if (network === 'crono') { - this.url = 'https://api.cronoscan.com/api'; - this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2'; - } - else if (network === 'fantom') { - this.url = 'https://api.ftmscan.com/api'; - this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX'; - } - else if (network === 'optimism') { - this.url = `https://api-optimistic.etherscan.io/api`; - this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U'; - } - else if (network === 'moonbeam') { - this.url = 'https://api-moonbeam.moonscan.io/api'; - this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ'; - } - else if (network === 'gnosis') { - this.url = 'https://api.gnosisscan.io/api'; - this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB'; - } - else if (network === 'scroll') { - this.url = 'https://api.scrollscan.com/api'; - this.apikey = '4V37ZJFIN9AURJSU9YG1RP3MSVTPH6D6Z4'; - } - else if (network === 'celo') { - this.url = 'https://api.celoscan.io/api'; - this.apikey = 'JBV78T5KP15W7WKKKD6KC4J8RX2F4PK8AF'; - } - else if (network === 'base') { - this.url = 'https://api.basescan.org/api'; - this.apikey = '9I5HUJHPD4ZNXJ4M8TZJ1HD2QBVP1U3M3J'; - } - else if (network === 'sonic') { - this.url = 'https://api.sonicscan.org/api'; - this.apikey = 'STCM7CPYP341C66C4IVV1IFMWDYRUTI1QY'; - } - else { - this.url = `https://api-${network}.etherscan.io/api`; + throw new Error( + `Invalid network "${network}". Must be one of ${exports.networks}`, + ) + } else if (network === 'mainnet') { + this.url = 'https://api.etherscan.io/api' + } else if (network === 'polygon') { + this.url = 'https://api.polygonscan.com/api' + this.apikey = 'AMHGNTV5A7XYGX2M781JB3RC1DZFVRWQEB' + } else if (network === 'arbitrum') { + this.url = 'https://api.arbiscan.io/api' + this.apikey = 'ZGTK2TAGWMAB6IAC12BMK8YYPNCPIM8VDQ' + } else if (network === 'avalanche') { + this.url = 'https://api.snowtrace.io/api' + this.apikey = 'U5FAN98S5XNH5VI83TI4H35R9I4TDCKEJY' + } else if (network === 'bsc') { + this.url = 'https://api.bscscan.com/api' + this.apikey = 'APYH49FXVY9UA3KTDI6F4WP3KPIC86NITN' + } else if (network === 'crono') { + this.url = 'https://api.cronoscan.com/api' + this.apikey = '76A3RG5WHTPMMR66E9SFI2EIDT6MP976W2' + } else if (network === 'fantom') { + this.url = 'https://api.ftmscan.com/api' + this.apikey = '71KRX13XPZMGR3D1Q85W78G2DSZ4JPMAEX' + } else if (network === 'optimism') { + this.url = 'https://api-optimistic.etherscan.io/api' + this.apikey = 'FEXS1HXVA4Y2RNTMEA8V1UTK21S4JWHH9U' + } else if (network === 'moonbeam') { + this.url = 'https://api-moonbeam.moonscan.io/api' + this.apikey = '5EUFXW6TDC16VERF3D9SCWRRU6AEMTBHNJ' + } else if (network === 'gnosis') { + this.url = 'https://api.gnosisscan.io/api' + this.apikey = '2RWGXIWK538EJ8XSP9DE2JUINSCG7UCSJB' + } else if (network === 'scroll') { + this.url = 'https://api.scrollscan.com/api' + this.apikey = '4V37ZJFIN9AURJSU9YG1RP3MSVTPH6D6Z4' + } else if (network === 'celo') { + this.url = 'https://api.celoscan.io/api' + this.apikey = 'JBV78T5KP15W7WKKKD6KC4J8RX2F4PK8AF' + } else if (network === 'base') { + this.url = 'https://api.basescan.org/api' + this.apikey = '9I5HUJHPD4ZNXJ4M8TZJ1HD2QBVP1U3M3J' + } else if (network === 'sonic') { + this.url = 'https://api.sonicscan.org/api' + this.apikey = 'STCM7CPYP341C66C4IVV1IFMWDYRUTI1QY' + } else { + this.url = `https://api-${network}.etherscan.io/api` } } + /** * Parses the verified source code files from Etherscan * @param contractAddress Ethereum contract address with a 0x prefix * @return Promise with an array of UmlClass objects */ async getUmlClasses(contractAddress) { - const { files, contractName, remappings } = await this.getSourceCode(contractAddress); - let umlClasses = []; + const { files, contractName, remappings } = + await this.getSourceCode(contractAddress) + let umlClasses = [] for (const file of files) { - debug(`Parsing source file ${file.filename}`); - const node = await this.parseSourceCode(file.code); - const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)(node, file.filename, remappings); - umlClasses = umlClasses.concat(umlClass); + debug(`Parsing source file ${file.filename}`) + const node = await this.parseSourceCode(file.code) + const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)( + node, + file.filename, + remappings, + ) + umlClasses = umlClasses.concat(umlClass) } return { umlClasses, contractName, - }; + } } + /** * Get Solidity code from Etherscan for a contract and merges all files * into one long string of Solidity code. @@ -126,52 +130,74 @@ class EtherscanParser { * @return Promise string of Solidity code */ async getSolidityCode(contractAddress, filename) { - const { files, contractName, compilerVersion, remappings } = await this.getSourceCode(contractAddress, filename); + const { files, contractName, compilerVersion, remappings } = + await this.getSourceCode(contractAddress, filename) // Parse the UmlClasses from the Solidity code in each file - let umlClasses = []; + let umlClasses = [] for (const file of files) { - const node = await this.parseSourceCode(file.code); - const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)(node, file.filename, remappings); - umlClasses = umlClasses.concat(umlClass); + const node = await this.parseSourceCode(file.code) + const umlClass = (0, converterAST2Classes_1.convertAST2UmlClasses)( + node, + file.filename, + remappings, + ) + umlClasses = umlClasses.concat(umlClass) } // Sort the classes so dependent code is first - const topologicalSortedClasses = (0, filterClasses_1.topologicalSortClasses)(umlClasses); + const topologicalSortedClasses = (0, + filterClasses_1.topologicalSortClasses)(umlClasses) // Get a list of filenames the classes are in - const sortedFilenames = topologicalSortedClasses.map((umlClass) => umlClass.relativePath); + const sortedFilenames = topologicalSortedClasses.map( + (umlClass) => umlClass.relativePath, + ) // Remove duplicate filenames from the list - const dependentFilenames = [...new Set(sortedFilenames)]; + const dependentFilenames = [...new Set(sortedFilenames)] // find any files that didn't have dependencies found - const nonDependentFiles = files.filter((f) => !dependentFilenames.includes(f.filename)); - const nonDependentFilenames = nonDependentFiles.map((f) => f.filename); + const nonDependentFiles = files.filter( + (f) => !dependentFilenames.includes(f.filename), + ) + const nonDependentFilenames = nonDependentFiles.map((f) => f.filename) if (nonDependentFilenames.length) { - debug(`Failed to find dependencies to files: ${nonDependentFilenames}`); + debug( + `Failed to find dependencies to files: ${nonDependentFilenames}`, + ) } - const solidityVersion = (0, regEx_1.parseSolidityVersion)(compilerVersion); - let solidityCode = `pragma solidity =${solidityVersion};\n`; + const solidityVersion = (0, regEx_1.parseSolidityVersion)( + compilerVersion, + ) + let solidityCode = `pragma solidity =${solidityVersion};\n` // output non dependent code before the dependent files just in case sol2uml missed some dependencies - const filenames = [...nonDependentFilenames, ...dependentFilenames]; + const filenames = [...nonDependentFilenames, ...dependentFilenames] // For each filename filenames.forEach((filename) => { // Lookup the file that contains the Solidity code - const file = files.find((f) => f.filename === filename); - if (!file) - throw Error(`Failed to find file with filename "${filename}"`); + const file = files.find((f) => f.filename === filename) + if (!file) { + throw Error(`Failed to find file with filename "${filename}"`) + } // comment out any pragma solidity lines as its set from the compiler version - const removedPragmaSolidity = file.code.replace(/(\s)(pragma\s+solidity.*;)/gm, '$1/* $2 */'); + const removedPragmaSolidity = file.code.replace( + /(\s)(pragma\s+solidity.*;)/gm, + '$1/* $2 */', + ) // comment out any import statements // match whitespace before import // and characters after import up to ; // replace all in file and match across multiple lines - const removedImports = removedPragmaSolidity.replace(/^\s*?(import.*?;)/gms, '/* $1 */'); + const removedImports = removedPragmaSolidity.replace( + /^\s*?(import.*?;)/gms, + '/* $1 */', + ) // Rename SPDX-License-Identifier to SPDX--License-Identifier so the merged file will compile - const removedSPDX = removedImports.replace(/SPDX-/, 'SPDX--'); - solidityCode += removedSPDX; - }); + const removedSPDX = removedImports.replace(/SPDX-/, 'SPDX--') + solidityCode += removedSPDX + }) return { solidityCode, contractName, - }; + } } + /** * Parses Solidity source code into an ASTNode object * @param sourceCode Solidity source code @@ -179,22 +205,27 @@ class EtherscanParser { */ async parseSourceCode(sourceCode) { try { - const node = (0, parser_1.parse)(sourceCode, {}); - return node; - } - catch (err) { - throw new Error(`Failed to parse solidity code from source code:\n${sourceCode}`, { cause: err }); + const node = (0, parser_1.parse)(sourceCode, {}) + return node + } catch (err) { + throw new Error( + `Failed to parse solidity code from source code:\n${sourceCode}`, + { cause: err }, + ) } } + /** * Calls Etherscan to get the verified source code for the specified contract address * @param contractAddress Ethereum contract address with a 0x prefix * @oaram filename optional, case-sensitive name of the source file without the .sol */ async getSourceCode(contractAddress, filename) { - const description = `get verified source code for address ${contractAddress} from Etherscan API.`; + const description = `get verified source code for address ${contractAddress} from Etherscan API.` try { - debug(`About to get Solidity source code for ${contractAddress} from ${this.url}`); + debug( + `About to get Solidity source code for ${contractAddress} from ${this.url}`, + ) const response = await axios_1.default.get(this.url, { params: { module: 'contract', @@ -202,62 +233,78 @@ class EtherscanParser { address: contractAddress, apikey: this.apikey, }, - }); + }) if (!Array.isArray(response?.data?.result)) { - throw new Error(`Failed to ${description}. No result array in HTTP data: ${JSON.stringify(response?.data)}`); + throw new Error( + `Failed to ${description}. No result array in HTTP data: ${JSON.stringify(response?.data)}`, + ) } - let remappings; + let remappings const results = response.data.result.map((result) => { if (!result.SourceCode) { - throw new Error(`Failed to ${description}. Most likely the contract has not been verified on Etherscan.`); + throw new Error( + `Failed to ${description}. Most likely the contract has not been verified on Etherscan.`, + ) } // if multiple Solidity source files if (result.SourceCode[0] === '{') { try { - let parableResultString = result.SourceCode; + let parableResultString = result.SourceCode // This looks like an Etherscan bug but we'll handle it here if (result.SourceCode[1] === '{') { // remove first { and last } from the SourceCode string so it can be JSON parsed - parableResultString = result.SourceCode.slice(1, -1); + parableResultString = result.SourceCode.slice(1, -1) } - const sourceCodeObject = JSON.parse(parableResultString); + const sourceCodeObject = JSON.parse(parableResultString) // Get any remapping of filenames from the settings - remappings = (0, exports.parseRemappings)(sourceCodeObject.settings?.remappings); + remappings = (0, exports.parseRemappings)( + sourceCodeObject.settings?.remappings, + ) // The getsource response from Etherscan is inconsistent so we need to handle both shapes const sourceFiles = sourceCodeObject.sources ? Object.entries(sourceCodeObject.sources) - : Object.entries(sourceCodeObject); + : Object.entries(sourceCodeObject) return sourceFiles.map(([filename, code]) => ({ code: code.content, filename, - })); - } - catch (err) { - throw new Error(`Failed to parse Solidity source code from Etherscan's SourceCode. ${result.SourceCode}`, { cause: err }); + })) + } catch (err) { + throw new Error( + `Failed to parse Solidity source code from Etherscan's SourceCode. ${result.SourceCode}`, + { cause: err }, + ) } } // if multiple Solidity source files with no Etherscan bug in the SourceCode field if (result?.SourceCode?.sources) { - const sourceFiles = Object.values(result.SourceCode.sources); + const sourceFiles = Object.values(result.SourceCode.sources) // Get any remapping of filenames from the settings - remappings = (0, exports.parseRemappings)(result.SourceCode.settings?.remappings); + remappings = (0, exports.parseRemappings)( + result.SourceCode.settings?.remappings, + ) return sourceFiles.map(([filename, code]) => ({ code: code.content, filename, - })); + })) } // Solidity source code was not uploaded into multiple files so is just in the SourceCode field return { code: result.SourceCode, filename: contractAddress, - }; - }); - let files = results.flat(1); - const filenameWithExt = filename + '.sol'; + } + }) + let files = results.flat(1) + const filenameWithExt = filename + '.sol' if (filename) { - files = files.filter((r) => path_1.default.parse(r.filename).base == filenameWithExt); + files = files.filter( + (r) => + path_1.default.parse(r.filename).base == + filenameWithExt, + ) if (!files?.length) { - throw new Error(`Failed to find source file "${filename}" for contract ${contractAddress}`); + throw new Error( + `Failed to find source file "${filename}" for contract ${contractAddress}`, + ) } } return { @@ -265,30 +312,33 @@ class EtherscanParser { contractName: response.data.result[0].ContractName, compilerVersion: response.data.result[0].CompilerVersion, remappings, - }; - } - catch (err) { + } + } catch (err) { if (err.message) { - throw err; + throw err } if (!err.response) { - throw new Error(`Failed to ${description}. No HTTP response.`); + throw new Error(`Failed to ${description}. No HTTP response.`) } - throw new Error(`Failed to ${description}. HTTP status code ${err.response?.status}, status text: ${err.response?.statusText}`, { cause: err }); + throw new Error( + `Failed to ${description}. HTTP status code ${err.response?.status}, status text: ${err.response?.statusText}`, + { cause: err }, + ) } } } -exports.EtherscanParser = EtherscanParser; +exports.EtherscanParser = EtherscanParser /** * Parses Ethersan's remappings config in its API response * @param rawMappings */ const parseRemappings = (rawMappings) => { - if (!rawMappings) - return []; - return rawMappings.map((mapping) => (0, exports.parseRemapping)(mapping)); -}; -exports.parseRemappings = parseRemappings; + if (!rawMappings) { + return [] + } + return rawMappings.map((mapping) => (0, exports.parseRemapping)(mapping)) +} +exports.parseRemappings = parseRemappings /** * Parses a single mapping. For example * "@openzeppelin/=lib/openzeppelin-contracts/" @@ -297,13 +347,13 @@ exports.parseRemappings = parseRemappings; * @param mapping */ const parseRemapping = (mapping) => { - const equalIndex = mapping.indexOf('='); - const from = mapping.slice(0, equalIndex); - const to = mapping.slice(equalIndex + 1); + const equalIndex = mapping.indexOf('=') + const from = mapping.slice(0, equalIndex) + const to = mapping.slice(equalIndex + 1) return { from: new RegExp('^' + from), to, - }; -}; -exports.parseRemapping = parseRemapping; -//# sourceMappingURL=parserEtherscan.js.map \ No newline at end of file + } +} +exports.parseRemapping = parseRemapping +// # sourceMappingURL=parserEtherscan.js.map diff --git a/lib/parserFiles.d.ts b/lib/parserFiles.d.ts index 9251bbc8..115e19e2 100644 --- a/lib/parserFiles.d.ts +++ b/lib/parserFiles.d.ts @@ -1,9 +1,21 @@ -import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types'; -import { UmlClass } from './umlClass'; -export declare const parseUmlClassesFromFiles: (filesOrFolders: readonly string[], ignoreFilesOrFolders: readonly string[], subfolders?: number) => Promise; -export declare function getSolidityFilesFromFolderOrFiles(folderOrFilePaths: readonly string[], ignoreFilesOrFolders: readonly string[], subfolders?: number): Promise; -export declare function getSolidityFilesFromFolderOrFile(folderOrFilePath: string, ignoreFilesOrFolders?: readonly string[], depthLimit?: number): Promise; -export declare function parseSolidityFile(fileName: string): ASTNode; -export declare const readFile: (fileName: string, extension?: string) => string; -export declare const isFile: (fileName: string) => boolean; -export declare const isFolder: (fileName: string) => boolean; +import { ASTNode } from '@solidity-parser/parser/dist/src/ast-types' +import { UmlClass } from './umlClass' +export declare const parseUmlClassesFromFiles: ( + filesOrFolders: readonly string[], + ignoreFilesOrFolders: readonly string[], + subfolders?: number, +) => Promise +export declare function getSolidityFilesFromFolderOrFiles( + folderOrFilePaths: readonly string[], + ignoreFilesOrFolders: readonly string[], + subfolders?: number, +): Promise +export declare function getSolidityFilesFromFolderOrFile( + folderOrFilePath: string, + ignoreFilesOrFolders?: readonly string[], + depthLimit?: number, +): Promise +export declare function parseSolidityFile(fileName: string): ASTNode +export declare const readFile: (fileName: string, extension?: string) => string +export declare const isFile: (fileName: string) => boolean +export declare const isFolder: (fileName: string) => boolean diff --git a/lib/parserFiles.js b/lib/parserFiles.js index 92e5346a..58f8a378 100644 --- a/lib/parserFiles.js +++ b/lib/parserFiles.js @@ -1,146 +1,190 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.isFolder = exports.isFile = exports.readFile = exports.parseSolidityFile = exports.getSolidityFilesFromFolderOrFile = exports.getSolidityFilesFromFolderOrFiles = exports.parseUmlClassesFromFiles = void 0; -const fs_1 = require("fs"); -const path_1 = require("path"); -const klaw_1 = __importDefault(require("klaw")); -const parser_1 = require("@solidity-parser/parser"); -const converterAST2Classes_1 = require("./converterAST2Classes"); -const debug = require('debug')('sol2uml'); -const parseUmlClassesFromFiles = async (filesOrFolders, ignoreFilesOrFolders, subfolders = -1) => { - const files = await getSolidityFilesFromFolderOrFiles(filesOrFolders, ignoreFilesOrFolders, subfolders); - let umlClasses = []; +'use strict' +const __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod } + } +Object.defineProperty(exports, '__esModule', { value: true }) +exports.isFolder = + exports.isFile = + exports.readFile = + exports.parseSolidityFile = + exports.getSolidityFilesFromFolderOrFile = + exports.getSolidityFilesFromFolderOrFiles = + exports.parseUmlClassesFromFiles = + void 0 +const fs_1 = require('fs') +const path_1 = require('path') +const klaw_1 = __importDefault(require('klaw')) +const parser_1 = require('@solidity-parser/parser') +const converterAST2Classes_1 = require('./converterAST2Classes') +const debug = require('debug')('sol2uml') +const parseUmlClassesFromFiles = async ( + filesOrFolders, + ignoreFilesOrFolders, + subfolders = -1, +) => { + const files = await getSolidityFilesFromFolderOrFiles( + filesOrFolders, + ignoreFilesOrFolders, + subfolders, + ) + let umlClasses = [] for (const file of files) { - const node = await parseSolidityFile(file); - const relativePath = (0, path_1.relative)(process.cwd(), file); - const newUmlClasses = (0, converterAST2Classes_1.convertAST2UmlClasses)(node, relativePath, [], true); - umlClasses = umlClasses.concat(newUmlClasses); + const node = await parseSolidityFile(file) + const relativePath = (0, path_1.relative)(process.cwd(), file) + const newUmlClasses = (0, converterAST2Classes_1.convertAST2UmlClasses)( + node, + relativePath, + [], + true, + ) + umlClasses = umlClasses.concat(newUmlClasses) } - return umlClasses; -}; -exports.parseUmlClassesFromFiles = parseUmlClassesFromFiles; -async function getSolidityFilesFromFolderOrFiles(folderOrFilePaths, ignoreFilesOrFolders, subfolders = -1) { - let files = []; + return umlClasses +} +exports.parseUmlClassesFromFiles = parseUmlClassesFromFiles +async function getSolidityFilesFromFolderOrFiles( + folderOrFilePaths, + ignoreFilesOrFolders, + subfolders = -1, +) { + let files = [] for (const folderOrFilePath of folderOrFilePaths) { - const result = await getSolidityFilesFromFolderOrFile(folderOrFilePath, ignoreFilesOrFolders, subfolders); - files = files.concat(result); + const result = await getSolidityFilesFromFolderOrFile( + folderOrFilePath, + ignoreFilesOrFolders, + subfolders, + ) + files = files.concat(result) } - return files; + return files } -exports.getSolidityFilesFromFolderOrFiles = getSolidityFilesFromFolderOrFiles; -function getSolidityFilesFromFolderOrFile(folderOrFilePath, ignoreFilesOrFolders = [], depthLimit = -1) { - debug(`About to get Solidity files under ${folderOrFilePath}`); +exports.getSolidityFilesFromFolderOrFiles = getSolidityFilesFromFolderOrFiles +function getSolidityFilesFromFolderOrFile( + folderOrFilePath, + ignoreFilesOrFolders = [], + depthLimit = -1, +) { + debug(`About to get Solidity files under ${folderOrFilePath}`) return new Promise((resolve, reject) => { try { - const folderOrFile = (0, fs_1.lstatSync)(folderOrFilePath); + const folderOrFile = (0, fs_1.lstatSync)(folderOrFilePath) if (folderOrFile.isDirectory()) { - const files = []; + const files = [] // filter out files or folders that are to be ignored const filter = (file) => { - return !ignoreFilesOrFolders.includes((0, path_1.basename)(file)); - }; - (0, klaw_1.default)(folderOrFilePath, { + return !ignoreFilesOrFolders.includes( + (0, path_1.basename)(file), + ) + } + ;(0, klaw_1.default)(folderOrFilePath, { depthLimit, filter, preserveSymlinks: true, }) .on('data', (file) => { - if ( - // If file has sol extension - (0, path_1.extname)(file.path) === '.sol' && - // and file and not a folder - // Note Foundry's forge outputs folders with the same name as the source file - file.stats.isFile()) - files.push(file.path); - }) + if ( + // If file has sol extension + (0, path_1.extname)(file.path) === '.sol' && + // and file and not a folder + // Note Foundry's forge outputs folders with the same name as the source file + file.stats.isFile() + ) { + files.push(file.path) + } + }) .on('end', () => { - // debug(`Got Solidity files to be parsed: ${files}`) - resolve(files); - }); - } - else if (folderOrFile.isFile()) { + // debug(`Got Solidity files to be parsed: ${files}`) + resolve(files) + }) + } else if (folderOrFile.isFile()) { if ((0, path_1.extname)(folderOrFilePath) === '.sol') { - debug(`Got Solidity file to be parsed: ${folderOrFilePath}`); - resolve([folderOrFilePath]); + debug(`Got Solidity file to be parsed: ${folderOrFilePath}`) + resolve([folderOrFilePath]) + } else { + reject( + Error( + `File ${folderOrFilePath} does not have a .sol extension.`, + ), + ) } - else { - reject(Error(`File ${folderOrFilePath} does not have a .sol extension.`)); - } - } - else { - reject(Error(`Could not find directory or file ${folderOrFilePath}`)); + } else { + reject( + Error( + `Could not find directory or file ${folderOrFilePath}`, + ), + ) } - } - catch (err) { - let error; + } catch (err) { + let error if (err?.code === 'ENOENT') { - error = Error(`No such file or folder ${folderOrFilePath}. Make sure you pass in the root directory of the contracts`); - } - else { - error = new Error(`Failed to get Solidity files under folder or file ${folderOrFilePath}`, { cause: err }); + error = Error( + `No such file or folder ${folderOrFilePath}. Make sure you pass in the root directory of the contracts`, + ) + } else { + error = new Error( + `Failed to get Solidity files under folder or file ${folderOrFilePath}`, + { cause: err }, + ) } - console.error(error); - reject(error); + console.error(error) + reject(error) } - }); + }) } -exports.getSolidityFilesFromFolderOrFile = getSolidityFilesFromFolderOrFile; +exports.getSolidityFilesFromFolderOrFile = getSolidityFilesFromFolderOrFile function parseSolidityFile(fileName) { - const solidityCode = (0, exports.readFile)(fileName); + const solidityCode = (0, exports.readFile)(fileName) try { - return (0, parser_1.parse)(solidityCode, {}); - } - catch (err) { + return (0, parser_1.parse)(solidityCode, {}) + } catch (err) { throw new Error(`Failed to parse solidity code in file ${fileName}.`, { cause: err, - }); + }) } } -exports.parseSolidityFile = parseSolidityFile; +exports.parseSolidityFile = parseSolidityFile const readFile = (fileName, extension) => { try { // try to read file with no extension - return (0, fs_1.readFileSync)(fileName, 'utf8'); - } - catch (err) { + return (0, fs_1.readFileSync)(fileName, 'utf8') + } catch (err) { if (!extension) { throw new Error(`Failed to read file "${fileName}".`, { cause: err, - }); + }) } try { // try to read file with extension - return (0, fs_1.readFileSync)(`${fileName}.${extension}`, 'utf8'); - } - catch (err) { - throw new Error(`Failed to read file "${fileName}" or "${fileName}.${extension}".`, { - cause: err, - }); + return (0, fs_1.readFileSync)(`${fileName}.${extension}`, 'utf8') + } catch (err) { + throw new Error( + `Failed to read file "${fileName}" or "${fileName}.${extension}".`, + { + cause: err, + }, + ) } } -}; -exports.readFile = readFile; +} +exports.readFile = readFile const isFile = (fileName) => { try { - const file = (0, fs_1.lstatSync)(fileName); - return file.isFile(); + const file = (0, fs_1.lstatSync)(fileName) + return file.isFile() + } catch (err) { + return false } - catch (err) { - return false; - } -}; -exports.isFile = isFile; +} +exports.isFile = isFile const isFolder = (fileName) => { try { - const file = (0, fs_1.lstatSync)(fileName); - return file.isDirectory(); + const file = (0, fs_1.lstatSync)(fileName) + return file.isDirectory() + } catch (err) { + return false } - catch (err) { - return false; - } -}; -exports.isFolder = isFolder; -//# sourceMappingURL=parserFiles.js.map \ No newline at end of file +} +exports.isFolder = isFolder +// # sourceMappingURL=parserFiles.js.map diff --git a/lib/parserGeneral.d.ts b/lib/parserGeneral.d.ts index 2d03ad06..cedd7b7f 100644 --- a/lib/parserGeneral.d.ts +++ b/lib/parserGeneral.d.ts @@ -1,18 +1,21 @@ -import { Network } from './parserEtherscan'; -import { UmlClass } from './umlClass'; +import { Network } from './parserEtherscan' +import { UmlClass } from './umlClass' export interface ParserOptions { - apiKey?: string; - network?: Network; - explorerUrl?: string; - subfolders?: string; - ignoreFilesOrFolders?: string[]; + apiKey?: string + network?: Network + explorerUrl?: string + subfolders?: string + ignoreFilesOrFolders?: string[] } /** * Parses Solidity source code from a local filesystem or verified code on Etherscan * @param fileFolderAddress filename, folder name or contract address * @param options of type `ParserOptions` */ -export declare const parserUmlClasses: (fileFolderAddress: string, options: ParserOptions) => Promise<{ - umlClasses: UmlClass[]; - contractName?: string; -}>; +export declare const parserUmlClasses: ( + fileFolderAddress: string, + options: ParserOptions, +) => Promise<{ + umlClasses: UmlClass[] + contractName?: string +}> diff --git a/lib/parserGeneral.js b/lib/parserGeneral.js index 73a4b021..e6738cb0 100644 --- a/lib/parserGeneral.js +++ b/lib/parserGeneral.js @@ -1,10 +1,10 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.parserUmlClasses = void 0; -const parserEtherscan_1 = require("./parserEtherscan"); -const parserFiles_1 = require("./parserFiles"); -const regEx_1 = require("./utils/regEx"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.parserUmlClasses = void 0 +const parserEtherscan_1 = require('./parserEtherscan') +const parserFiles_1 = require('./parserFiles') +const regEx_1 = require('./utils/regEx') +const debug = require('debug')('sol2uml') /** * Parses Solidity source code from a local filesystem or verified code on Etherscan * @param fileFolderAddress filename, folder name or contract address @@ -13,23 +13,35 @@ const debug = require('debug')('sol2uml'); const parserUmlClasses = async (fileFolderAddress, options) => { let result = { umlClasses: [], - }; - if ((0, regEx_1.isAddress)(fileFolderAddress)) { - debug(`argument ${fileFolderAddress} is an Ethereum address so checking Etherscan for the verified source code`); - const etherscanApiKey = options.apiKey || 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1'; - const etherscanParser = new parserEtherscan_1.EtherscanParser(etherscanApiKey, options.network, options.explorerUrl); - result = await etherscanParser.getUmlClasses(fileFolderAddress); } - else { - const subfolders = parseInt(options.subfolders); + if ((0, regEx_1.isAddress)(fileFolderAddress)) { + debug( + `argument ${fileFolderAddress} is an Ethereum address so checking Etherscan for the verified source code`, + ) + const etherscanApiKey = + options.apiKey || 'ZAD4UI2RCXCQTP38EXS3UY2MPHFU5H9KB1' + const etherscanParser = new parserEtherscan_1.EtherscanParser( + etherscanApiKey, + options.network, + options.explorerUrl, + ) + result = await etherscanParser.getUmlClasses(fileFolderAddress) + } else { + const subfolders = parseInt(options.subfolders) if (isNaN(subfolders)) { - console.error(`subfolders option must be an integer. Not ${options.subfolders}`); - process.exit(1); + console.error( + `subfolders option must be an integer. Not ${options.subfolders}`, + ) + process.exit(1) } - const filesFolders = fileFolderAddress.split(','); - result.umlClasses = await (0, parserFiles_1.parseUmlClassesFromFiles)(filesFolders, options.ignoreFilesOrFolders || [], subfolders); + const filesFolders = fileFolderAddress.split(',') + result.umlClasses = await (0, parserFiles_1.parseUmlClassesFromFiles)( + filesFolders, + options.ignoreFilesOrFolders || [], + subfolders, + ) } - return result; -}; -exports.parserUmlClasses = parserUmlClasses; -//# sourceMappingURL=parserGeneral.js.map \ No newline at end of file + return result +} +exports.parserUmlClasses = parserUmlClasses +// # sourceMappingURL=parserGeneral.js.map diff --git a/lib/slotValues.d.ts b/lib/slotValues.d.ts index a67e62b1..8548b0ff 100644 --- a/lib/slotValues.d.ts +++ b/lib/slotValues.d.ts @@ -1,5 +1,5 @@ -import { BigNumberish } from '@ethersproject/bignumber'; -import { StorageSection, Variable } from './converterClasses2Storage'; +import { BigNumberish } from '@ethersproject/bignumber' +import { StorageSection, Variable } from './converterClasses2Storage' /** * Adds the slot values to the variables in the storage section. * This can be rerun for a section as it will only get if the slot value @@ -11,8 +11,14 @@ import { StorageSection, Variable } from './converterClasses2Storage'; * @param arrayItems the number of items to display at the start and end of an array * @param blockTag block number or `latest` */ -export declare const addSlotValues: (url: string, contractAddress: string, storageSection: StorageSection, arrayItems: number, blockTag: BigNumberish) => Promise; -export declare const parseValue: (variable: Variable) => string; +export declare const addSlotValues: ( + url: string, + contractAddress: string, + storageSection: StorageSection, + arrayItems: number, + blockTag: BigNumberish, +) => Promise +export declare const parseValue: (variable: Variable) => string /** * Get storage slot values from JSON-RPC API provider. * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy @@ -22,7 +28,12 @@ export declare const parseValue: (variable: Variable) => string; * @param blockTag block number or `latest` * @return slotValues array of 32 byte slot values as hexadecimal strings */ -export declare const getSlotValues: (url: string, contractAddress: string, slotKeys: readonly BigNumberish[], blockTag?: BigNumberish | 'latest') => Promise; +export declare const getSlotValues: ( + url: string, + contractAddress: string, + slotKeys: readonly BigNumberish[], + blockTag?: BigNumberish | 'latest', +) => Promise /** * Get storage slot values from JSON-RPC API provider. * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy @@ -32,7 +43,12 @@ export declare const getSlotValues: (url: string, contractAddress: string, slotK * @param blockTag block number or `latest` * @return slotValue 32 byte slot value as hexadecimal string */ -export declare const getSlotValue: (url: string, contractAddress: string, slotKey: BigNumberish, blockTag: BigNumberish | 'latest') => Promise; +export declare const getSlotValue: ( + url: string, + contractAddress: string, + slotKey: BigNumberish, + blockTag: BigNumberish | 'latest', +) => Promise /** * Calculates the number of string characters or bytes of a string or bytes type. * See the following for how string and bytes are stored in storage slots @@ -41,9 +57,9 @@ export declare const getSlotValue: (url: string, contractAddress: string, slotKe * @return bytes the number of bytes of the dynamic slot. If static, zero is return. */ export declare const dynamicSlotSize: (variable: { - name?: string; - type?: string; - slotValue?: string; -}) => number; -export declare const convert2String: (bytes: string) => string; -export declare const escapeString: (text: string) => string; + name?: string + type?: string + slotValue?: string +}) => number +export declare const convert2String: (bytes: string) => string +export declare const escapeString: (text: string) => string diff --git a/lib/slotValues.js b/lib/slotValues.js index 281b1307..f16bf6b8 100644 --- a/lib/slotValues.js +++ b/lib/slotValues.js @@ -1,16 +1,25 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.escapeString = exports.convert2String = exports.dynamicSlotSize = exports.getSlotValue = exports.getSlotValues = exports.parseValue = exports.addSlotValues = void 0; -const bignumber_1 = require("@ethersproject/bignumber"); -const axios_1 = __importDefault(require("axios")); -const umlClass_1 = require("./umlClass"); -const utils_1 = require("ethers/lib/utils"); -const SlotValueCache_1 = require("./SlotValueCache"); -const ethers_1 = require("ethers"); -const debug = require('debug')('sol2uml'); +'use strict' +const __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod } + } +Object.defineProperty(exports, '__esModule', { value: true }) +exports.escapeString = + exports.convert2String = + exports.dynamicSlotSize = + exports.getSlotValue = + exports.getSlotValues = + exports.parseValue = + exports.addSlotValues = + void 0 +const bignumber_1 = require('@ethersproject/bignumber') +const axios_1 = __importDefault(require('axios')) +const umlClass_1 = require('./umlClass') +const utils_1 = require('ethers/lib/utils') +const SlotValueCache_1 = require('./SlotValueCache') +const ethers_1 = require('ethers') +const debug = require('debug')('sol2uml') /** * Adds the slot values to the variables in the storage section. * This can be rerun for a section as it will only get if the slot value @@ -22,179 +31,220 @@ const debug = require('debug')('sol2uml'); * @param arrayItems the number of items to display at the start and end of an array * @param blockTag block number or `latest` */ -const addSlotValues = async (url, contractAddress, storageSection, arrayItems, blockTag) => { - const valueVariables = storageSection.variables.filter((variable) => variable.getValue && !variable.slotValue); - if (valueVariables.length === 0) - return; +const addSlotValues = async ( + url, + contractAddress, + storageSection, + arrayItems, + blockTag, +) => { + const valueVariables = storageSection.variables.filter( + (variable) => variable.getValue && !variable.slotValue, + ) + if (valueVariables.length === 0) { + return + } // for each variable, add all the slots used by the variable. - const slots = []; + const slots = [] valueVariables.forEach((variable) => { if (variable.offset) { - slots.push(bignumber_1.BigNumber.from(variable.offset)); - } - else { + slots.push(bignumber_1.BigNumber.from(variable.offset)) + } else { for (let i = 0; variable.fromSlot + i <= variable.toSlot; i++) { - if (variable.attributeType === umlClass_1.AttributeType.Array && + if ( + variable.attributeType === umlClass_1.AttributeType.Array && i >= arrayItems && - i < variable.toSlot - arrayItems) { - continue; + i < variable.toSlot - arrayItems + ) { + continue } - slots.push(variable.fromSlot + i); + slots.push(variable.fromSlot + i) } } - }); + }) // remove duplicate slot numbers - const uniqueFromSlots = [...new Set(slots)]; + const uniqueFromSlots = [...new Set(slots)] // Convert slot numbers to BigNumbers and offset dynamic arrays - let slotKeys = uniqueFromSlots.map((fromSlot) => { + const slotKeys = uniqueFromSlots.map((fromSlot) => { if (storageSection.offset) { - return bignumber_1.BigNumber.from(storageSection.offset).add(fromSlot); + return bignumber_1.BigNumber.from(storageSection.offset).add( + fromSlot, + ) } - return bignumber_1.BigNumber.from(fromSlot); - }); + return bignumber_1.BigNumber.from(fromSlot) + }) // Get the contract slot values from the node provider - const values = await (0, exports.getSlotValues)(url, contractAddress, slotKeys, blockTag); + const values = await (0, exports.getSlotValues)( + url, + contractAddress, + slotKeys, + blockTag, + ) // For each slot value retrieved values.forEach((value, i) => { // Get the corresponding slot number for the slot value - const fromSlot = uniqueFromSlots[i]; + const fromSlot = uniqueFromSlots[i] // For each variable in the storage section for (const variable of storageSection.variables) { - if (variable.getValue && + if ( + variable.getValue && variable.offset && - bignumber_1.BigNumber.from(variable.offset).eq(fromSlot)) { - debug(`Set slot value ${value} for section "${storageSection.name}", var type ${variable.type}, slot ${variable.offset}`); - variable.slotValue = value; + bignumber_1.BigNumber.from(variable.offset).eq(fromSlot) + ) { + debug( + `Set slot value ${value} for section "${storageSection.name}", var type ${variable.type}, slot ${variable.offset}`, + ) + variable.slotValue = value // parse variable value from slot data if (variable.displayValue) { - variable.parsedValue = (0, exports.parseValue)(variable); + variable.parsedValue = (0, exports.parseValue)(variable) } - } - else if (variable.getValue && variable.fromSlot === fromSlot) { - debug(`Set slot value ${value} for section "${storageSection.name}", var type ${variable.type}, slot ${variable.fromSlot} offset ${storageSection.offset}`); - variable.slotValue = value; + } else if (variable.getValue && variable.fromSlot === fromSlot) { + debug( + `Set slot value ${value} for section "${storageSection.name}", var type ${variable.type}, slot ${variable.fromSlot} offset ${storageSection.offset}`, + ) + variable.slotValue = value // parse variable value from slot data if (variable.displayValue) { - variable.parsedValue = (0, exports.parseValue)(variable); + variable.parsedValue = (0, exports.parseValue)(variable) } } // if variable is past the slot that has the value - else if (variable.toSlot && - bignumber_1.BigNumber.from(variable.toSlot).gt(fromSlot)) { - break; + else if ( + variable.toSlot && + bignumber_1.BigNumber.from(variable.toSlot).gt(fromSlot) + ) { + break } } - }); -}; -exports.addSlotValues = addSlotValues; + }) +} +exports.addSlotValues = addSlotValues const parseValue = (variable) => { - if (!variable.slotValue) - return undefined; - const start = 66 - (variable.byteOffset + variable.byteSize) * 2; - const end = 66 - variable.byteOffset * 2; - const variableValue = variable.slotValue.substring(start, end); + if (!variable.slotValue) { + return undefined + } + const start = 66 - (variable.byteOffset + variable.byteSize) * 2 + const end = 66 - variable.byteOffset * 2 + const variableValue = variable.slotValue.substring(start, end) try { // Contracts, structs and enums if (variable.attributeType === umlClass_1.AttributeType.UserDefined) { - return parseUserDefinedValue(variable, variableValue); + return parseUserDefinedValue(variable, variableValue) + } + if (variable.attributeType === umlClass_1.AttributeType.Elementary) { + return parseElementaryValue(variable, variableValue) } - if (variable.attributeType === umlClass_1.AttributeType.Elementary) - return parseElementaryValue(variable, variableValue); // dynamic arrays - if (variable.attributeType === umlClass_1.AttributeType.Array && - variable.dynamic) { - return (0, utils_1.formatUnits)('0x' + variableValue, 0); + if ( + variable.attributeType === umlClass_1.AttributeType.Array && + variable.dynamic + ) { + return (0, utils_1.formatUnits)('0x' + variableValue, 0) } - return undefined; + return undefined + } catch (err) { + throw Error( + `Failed to parse variable ${variable.name} of type ${variable.type}, value "${variableValue}"`, + { cause: err }, + ) } - catch (err) { - throw Error(`Failed to parse variable ${variable.name} of type ${variable.type}, value "${variableValue}"`, { cause: err }); - } -}; -exports.parseValue = parseValue; +} +exports.parseValue = parseValue const parseUserDefinedValue = (variable, variableValue) => { // TODO need to handle User Defined Value Types introduced in Solidity // https://docs.soliditylang.org/en/v0.8.18/types.html#user-defined-value-types // https://blog.soliditylang.org/2021/09/27/user-defined-value-types/ // using byteSize is crude and will be incorrect for aliases types like int160 or uint160 if (variable.byteSize === 20) { - return (0, utils_1.getAddress)('0x' + variableValue); + return (0, utils_1.getAddress)('0x' + variableValue) } // this will also be wrong if the alias is to a 1 byte type. eg bytes1, int8 or uint8 if (variable.byteSize === 1) { // assume 1 byte is an enum so convert value to enum index number - const index = bignumber_1.BigNumber.from('0x' + variableValue).toNumber(); + const index = bignumber_1.BigNumber.from( + '0x' + variableValue, + ).toNumber() // lookup enum value if its available - return variable?.enumValues ? variable?.enumValues[index] : undefined; + return variable?.enumValues ? variable?.enumValues[index] : undefined } // we don't parse if a struct which has a size of 32 bytes - return undefined; -}; + return undefined +} const parseElementaryValue = (variable, variableValue) => { // Elementary types if (variable.type === 'bool') { - if (variableValue === '00') - return 'false'; - if (variableValue === '01') - return 'true'; - throw Error(`Failed to parse bool variable "${variable.name}" in slot ${variable.fromSlot}, offset ${variable.byteOffset} and slot value "${variableValue}"`); + if (variableValue === '00') { + return 'false' + } + if (variableValue === '01') { + return 'true' + } + throw Error( + `Failed to parse bool variable "${variable.name}" in slot ${variable.fromSlot}, offset ${variable.byteOffset} and slot value "${variableValue}"`, + ) } if (variable.type === 'string' || variable.type === 'bytes') { if (variable.dynamic) { - const lastByte = variable.slotValue.slice(-2); - const size = bignumber_1.BigNumber.from('0x' + lastByte); + const lastByte = variable.slotValue.slice(-2) + const size = bignumber_1.BigNumber.from('0x' + lastByte) // Check if the last bit is set by AND the size with 0x01 if (size.and(1).eq(1)) { // Return the number of chars or bytes return bignumber_1.BigNumber.from(variable.slotValue) .sub(1) .div(2) - .toString(); + .toString() } // The last byte holds the length of the string or bytes in the slot - const valueHex = '0x' + variableValue.slice(0, size.toNumber()); - if (variable.type === 'bytes') - return valueHex; - return `\\"${(0, exports.convert2String)(valueHex)}\\"`; + const valueHex = '0x' + variableValue.slice(0, size.toNumber()) + if (variable.type === 'bytes') { + return valueHex + } + return `\\"${(0, exports.convert2String)(valueHex)}\\"` } - if (variable.type === 'bytes') - return '0x' + variableValue; - return `\\"${(0, exports.convert2String)('0x' + variableValue)}\\"`; + if (variable.type === 'bytes') { + return '0x' + variableValue + } + return `\\"${(0, exports.convert2String)('0x' + variableValue)}\\"` } if (variable.type === 'address') { - return (0, utils_1.getAddress)('0x' + variableValue); + return (0, utils_1.getAddress)('0x' + variableValue) } if (variable.type.match(/^uint([0-9]*)$/)) { - const parsedValue = (0, utils_1.formatUnits)('0x' + variableValue, 0); - return (0, utils_1.commify)(parsedValue); + const parsedValue = (0, utils_1.formatUnits)('0x' + variableValue, 0) + return (0, utils_1.commify)(parsedValue) } if (variable.type.match(/^bytes([0-9]+)$/)) { - return '0x' + variableValue; + return '0x' + variableValue } if (variable.type.match(/^int([0-9]*)/)) { // parse variable value as an unsigned number - let rawValue = bignumber_1.BigNumber.from('0x' + variableValue); + let rawValue = bignumber_1.BigNumber.from('0x' + variableValue) // parse the number of bits - const result = variable.type.match(/^int([0-9]*$)/); - const bitSize = result[1] ? result[1] : 256; + const result = variable.type.match(/^int([0-9]*$)/) + const bitSize = result[1] ? result[1] : 256 // Convert the number of bits to the number of hex characters - const hexSize = bignumber_1.BigNumber.from(bitSize).div(4).toNumber(); + const hexSize = bignumber_1.BigNumber.from(bitSize).div(4).toNumber() // bit mask has a leading 1 and the rest 0. 0x8 = 1000 binary - const mask = '0x80' + '0'.repeat(hexSize - 2); + const mask = '0x80' + '0'.repeat(hexSize - 2) // is the first bit a 1? - const negative = rawValue.and(mask); + const negative = rawValue.and(mask) if (negative.gt(0)) { // Convert unsigned number to a signed negative - const negativeOne = '0xFF' + 'F'.repeat(hexSize - 2); - rawValue = bignumber_1.BigNumber.from(negativeOne).sub(rawValue).add(1).mul(-1); + const negativeOne = '0xFF' + 'F'.repeat(hexSize - 2) + rawValue = bignumber_1.BigNumber.from(negativeOne) + .sub(rawValue) + .add(1) + .mul(-1) } - const parsedValue = (0, utils_1.formatUnits)(rawValue, 0); - return (0, utils_1.commify)(parsedValue); + const parsedValue = (0, utils_1.formatUnits)(rawValue, 0) + return (0, utils_1.commify)(parsedValue) } // add fixed point numbers when they are supported by Solidity - return undefined; -}; -let jsonRpcId = 0; + return undefined +} +let jsonRpcId = 0 /** * Get storage slot values from JSON-RPC API provider. * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy @@ -204,59 +254,80 @@ let jsonRpcId = 0; * @param blockTag block number or `latest` * @return slotValues array of 32 byte slot values as hexadecimal strings */ -const getSlotValues = async (url, contractAddress, slotKeys, blockTag = 'latest') => { +const getSlotValues = async ( + url, + contractAddress, + slotKeys, + blockTag = 'latest', +) => { try { if (slotKeys.length === 0) { - return []; + return [] } - const block = blockTag === 'latest' - ? blockTag - : (0, utils_1.hexValue)(bignumber_1.BigNumber.from(blockTag)); + const block = + blockTag === 'latest' + ? blockTag + : (0, utils_1.hexValue)(bignumber_1.BigNumber.from(blockTag)) // get cached values and missing slot keys from the cache - const { cachedValues, missingKeys } = SlotValueCache_1.SlotValueCache.readSlotValues(slotKeys); + const { cachedValues, missingKeys } = + SlotValueCache_1.SlotValueCache.readSlotValues(slotKeys) // If all values are in the cache then just return the cached values if (missingKeys.length === 0) { - return cachedValues; + return cachedValues } // Check we are pointing to the correct chain by checking the contract has code - const provider = new ethers_1.ethers.providers.JsonRpcProvider(url); - const code = await provider.getCode(contractAddress, block); + const provider = new ethers_1.ethers.providers.JsonRpcProvider(url) + const code = await provider.getCode(contractAddress, block) if (!code || code === '0x') { - const msg = `Address ${contractAddress} has no code. Check your "-u, --url" option or "NODE_URL" environment variable is pointing to the correct node.\nurl: ${url}`; - console.error(msg); - throw Error(msg); + const msg = `Address ${contractAddress} has no code. Check your "-u, --url" option or "NODE_URL" environment variable is pointing to the correct node.\nurl: ${url}` + console.error(msg) + throw Error(msg) } - debug(`About to get ${slotKeys.length} storage values for ${contractAddress} at block ${blockTag} from slot ${missingKeys[0].toString()}`); + debug( + `About to get ${slotKeys.length} storage values for ${contractAddress} at block ${blockTag} from slot ${missingKeys[0].toString()}`, + ) // Get the values for the missing slot keys const payload = missingKeys.map((key) => ({ id: (jsonRpcId++).toString(), jsonrpc: '2.0', method: 'eth_getStorageAt', params: [contractAddress, key, block], - })); - const response = await axios_1.default.post(url, payload); + })) + const response = await axios_1.default.post(url, payload) if (response.data?.error?.message) { - throw Error(response.data.error.message); + throw Error(response.data.error.message) } if (response.data.length !== missingKeys.length) { - throw Error(`Requested ${missingKeys.length} storage slot values but only got ${response.data.length}`); + throw Error( + `Requested ${missingKeys.length} storage slot values but only got ${response.data.length}`, + ) } - const responseData = response.data; - const sortedResponses = responseData.sort((a, b) => bignumber_1.BigNumber.from(a.id).gt(b.id) ? 1 : -1); + const responseData = response.data + const sortedResponses = responseData.sort((a, b) => + bignumber_1.BigNumber.from(a.id).gt(b.id) ? 1 : -1, + ) const missingValues = sortedResponses.map((data) => { if (data.error) { - throw Error(`json rpc call with id ${data.id} failed to get storage values: ${data.error?.message}`); + throw Error( + `json rpc call with id ${data.id} failed to get storage values: ${data.error?.message}`, + ) } - return '0x' + data.result.toUpperCase().slice(2); - }); + return '0x' + data.result.toUpperCase().slice(2) + }) // add new values to the cache and return the merged slot values - return SlotValueCache_1.SlotValueCache.addSlotValues(slotKeys, missingKeys, missingValues); - } - catch (err) { - throw Error(`Failed to get ${slotKeys.length} storage values for contract ${contractAddress} from ${url}`, { cause: err }); + return SlotValueCache_1.SlotValueCache.addSlotValues( + slotKeys, + missingKeys, + missingValues, + ) + } catch (err) { + throw Error( + `Failed to get ${slotKeys.length} storage values for contract ${contractAddress} from ${url}`, + { cause: err }, + ) } -}; -exports.getSlotValues = getSlotValues; +} +exports.getSlotValues = getSlotValues /** * Get storage slot values from JSON-RPC API provider. * @param url of Ethereum JSON-RPC API provider. eg Infura or Alchemy @@ -267,11 +338,16 @@ exports.getSlotValues = getSlotValues; * @return slotValue 32 byte slot value as hexadecimal string */ const getSlotValue = async (url, contractAddress, slotKey, blockTag) => { - debug(`About to get storage slot ${slotKey} value for ${contractAddress}`); - const values = await (0, exports.getSlotValues)(url, contractAddress, [slotKey], blockTag); - return values[0]; -}; -exports.getSlotValue = getSlotValue; + debug(`About to get storage slot ${slotKey} value for ${contractAddress}`) + const values = await (0, exports.getSlotValues)( + url, + contractAddress, + [slotKey], + blockTag, + ) + return values[0] +} +exports.getSlotValue = getSlotValue /** * Calculates the number of string characters or bytes of a string or bytes type. * See the following for how string and bytes are stored in storage slots @@ -281,33 +357,41 @@ exports.getSlotValue = getSlotValue; */ const dynamicSlotSize = (variable) => { try { - if (!variable?.slotValue) - throw Error(`Missing slot value.`); - const last4bits = '0x' + variable.slotValue.slice(-1); - const last4bitsNum = bignumber_1.BigNumber.from(last4bits).toNumber(); + if (!variable?.slotValue) { + throw Error('Missing slot value.') + } + const last4bits = '0x' + variable.slotValue.slice(-1) + const last4bitsNum = bignumber_1.BigNumber.from(last4bits).toNumber() // If the last 4 bits is an even number then it's not a dynamic slot - if (last4bitsNum % 2 === 0) - return 0; - const sizeRaw = bignumber_1.BigNumber.from(variable.slotValue).toNumber(); + if (last4bitsNum % 2 === 0) { + return 0 + } + const sizeRaw = bignumber_1.BigNumber.from( + variable.slotValue, + ).toNumber() // Adjust the size to bytes - return (sizeRaw - 1) / 2; - } - catch (err) { - throw Error(`Failed to calculate dynamic slot size for variable "${variable?.name}" of type "${variable?.type}" with slot value ${variable?.slotValue}`, { cause: err }); + return (sizeRaw - 1) / 2 + } catch (err) { + throw Error( + `Failed to calculate dynamic slot size for variable "${variable?.name}" of type "${variable?.type}" with slot value ${variable?.slotValue}`, + { cause: err }, + ) } -}; -exports.dynamicSlotSize = dynamicSlotSize; +} +exports.dynamicSlotSize = dynamicSlotSize const convert2String = (bytes) => { - if (bytes === - '0x0000000000000000000000000000000000000000000000000000000000000000') { - return ''; + if ( + bytes === + '0x0000000000000000000000000000000000000000000000000000000000000000' + ) { + return '' } - const rawString = (0, utils_1.toUtf8String)(bytes); - return (0, exports.escapeString)(rawString); -}; -exports.convert2String = convert2String; + const rawString = (0, utils_1.toUtf8String)(bytes) + return (0, exports.escapeString)(rawString) +} +exports.convert2String = convert2String const escapeString = (text) => { - return text.replace(/(?=[<>&"])/g, '\\'); -}; -exports.escapeString = escapeString; -//# sourceMappingURL=slotValues.js.map \ No newline at end of file + return text.replace(/(?=[<>&"])/g, '\\') +} +exports.escapeString = escapeString +// # sourceMappingURL=slotValues.js.map diff --git a/lib/sol2uml.d.ts b/lib/sol2uml.d.ts index 237decae..48e952ac 100644 --- a/lib/sol2uml.d.ts +++ b/lib/sol2uml.d.ts @@ -1,2 +1,2 @@ #! /usr/bin/env node -export {}; +export {} diff --git a/lib/sol2uml.js b/lib/sol2uml.js index 740345ad..7aa2eb65 100755 --- a/lib/sol2uml.js +++ b/lib/sol2uml.js @@ -1,69 +1,146 @@ #! /usr/bin/env node -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -const commander_1 = require("commander"); -const path_1 = require("path"); -const converterClasses2Dot_1 = require("./converterClasses2Dot"); -const converterClasses2Storage_1 = require("./converterClasses2Storage"); -const converterStorage2Dot_1 = require("./converterStorage2Dot"); -const diffContracts_1 = require("./diffContracts"); -const filterClasses_1 = require("./filterClasses"); -const parserEtherscan_1 = require("./parserEtherscan"); -const parserGeneral_1 = require("./parserGeneral"); -const squashClasses_1 = require("./squashClasses"); -const slotValues_1 = require("./slotValues"); -const regEx_1 = require("./utils/regEx"); -const validators_1 = require("./utils/validators"); -const writerFiles_1 = require("./writerFiles"); -const block_1 = require("./utils/block"); -const clc = require('cli-color'); -const program = new commander_1.Command(); -const debugControl = require('debug'); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +const commander_1 = require('commander') +const path_1 = require('path') +const converterClasses2Dot_1 = require('./converterClasses2Dot') +const converterClasses2Storage_1 = require('./converterClasses2Storage') +const converterStorage2Dot_1 = require('./converterStorage2Dot') +const diffContracts_1 = require('./diffContracts') +const filterClasses_1 = require('./filterClasses') +const parserEtherscan_1 = require('./parserEtherscan') +const parserGeneral_1 = require('./parserGeneral') +const squashClasses_1 = require('./squashClasses') +const slotValues_1 = require('./slotValues') +const regEx_1 = require('./utils/regEx') +const validators_1 = require('./utils/validators') +const writerFiles_1 = require('./writerFiles') +const block_1 = require('./utils/block') +const clc = require('cli-color') +const program = new commander_1.Command() +const debugControl = require('debug') +const debug = require('debug')('sol2uml') program .usage('[command] ') - .description(`Generate UML class or storage diagrams from local Solidity code or verified Solidity code on Etherscan-like explorers. -Can also flatten or compare verified source files on Etherscan-like explorers.`) - .addOption(new commander_1.Option('-sf, --subfolders ', 'number of subfolders that will be recursively searched for Solidity files.').default('-1', 'all')) - .addOption(new commander_1.Option('-f, --outputFormat ', 'output file format.') - .choices(['svg', 'png', 'dot', 'all']) - .default('svg')) + .description( + `Generate UML class or storage diagrams from local Solidity code or verified Solidity code on Etherscan-like explorers. +Can also flatten or compare verified source files on Etherscan-like explorers.`, + ) + .addOption( + new commander_1.Option( + '-sf, --subfolders ', + 'number of subfolders that will be recursively searched for Solidity files.', + ).default('-1', 'all'), + ) + .addOption( + new commander_1.Option( + '-f, --outputFormat ', + 'output file format.', + ) + .choices(['svg', 'png', 'dot', 'all']) + .default('svg'), + ) .option('-o, --outputFileName ', 'output file name') - .option('-i, --ignoreFilesOrFolders ', 'comma-separated list of files or folders to ignore', validators_1.validateNames) - .addOption(new commander_1.Option('-n, --network ', 'Ethereum network which maps to a blockchain explorer') - .choices(parserEtherscan_1.networks) - .default('mainnet') - .env('ETH_NETWORK')) - .addOption(new commander_1.Option('-e, --explorerUrl ', 'Override the `network` option with a custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api').env('EXPLORER_URL')) - .addOption(new commander_1.Option('-k, --apiKey ', 'Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan, SonicScan or SnowTrace API key').env('SCAN_API_KEY')) - .option('-bc, --backColor ', 'Canvas background color. "none" will use a transparent canvas.', 'white') - .option('-sc, --shapeColor ', 'Basic drawing color for graphics, not text', 'black') - .option('-fc, --fillColor ', 'Color used to fill the background of a node', 'gray95') + .option( + '-i, --ignoreFilesOrFolders ', + 'comma-separated list of files or folders to ignore', + validators_1.validateNames, + ) + .addOption( + new commander_1.Option( + '-n, --network ', + 'Ethereum network which maps to a blockchain explorer', + ) + .choices(parserEtherscan_1.networks) + .default('mainnet') + .env('ETH_NETWORK'), + ) + .addOption( + new commander_1.Option( + '-e, --explorerUrl ', + 'Override the `network` option with a custom blockchain explorer API URL. eg Polygon Mumbai testnet https://api-testnet.polygonscan.com/api', + ).env('EXPLORER_URL'), + ) + .addOption( + new commander_1.Option( + '-k, --apiKey ', + 'Blockchain explorer API key. eg Etherscan, Arbiscan, Optimism, BscScan, CronoScan, FTMScan, PolygonScan, SonicScan or SnowTrace API key', + ).env('SCAN_API_KEY'), + ) + .option( + '-bc, --backColor ', + 'Canvas background color. "none" will use a transparent canvas.', + 'white', + ) + .option( + '-sc, --shapeColor ', + 'Basic drawing color for graphics, not text', + 'black', + ) + .option( + '-fc, --fillColor ', + 'Color used to fill the background of a node', + 'gray95', + ) .option('-tc, --textColor ', 'Color used for text', 'black') - .option('-v, --verbose', 'run with debugging statements', false); -const version = (0, path_1.basename)(__dirname) === 'lib' - ? require('../package.json').version // used when run from compile js in /lib - : require('../../package.json').version; // used when run from TypeScript source files under src/ts via ts-node -program.version(version); + .option('-v, --verbose', 'run with debugging statements', false) +const version = + (0, path_1.basename)(__dirname) === 'lib' + ? require('../package.json').version // used when run from compile js in /lib + : require('../../package.json').version // used when run from TypeScript source files under src/ts via ts-node +program.version(version) const argumentText = `file name, folder(s) or contract address. \t\t\t\t When a folder is used, all *.sol files in that folder and all sub folders are used. \t\t\t\t A comma-separated list of files and folders can also be used. For example, \t\t\t\t\tsol2uml contracts,node_modules/@openzeppelin \t\t\t\t If an Ethereum address with a 0x prefix is passed, the verified source code from Etherscan will be used. For example -\t\t\t\t\tsol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9`; +\t\t\t\t\tsol2uml 0x79fEbF6B9F76853EDBcBc913e6aAE8232cFB9De9` program .command('class', { isDefault: true }) .usage('[options] ') .description('Generates a UML class diagram from Solidity source code.') .argument('fileFolderAddress', argumentText) - .option('-b, --baseContractNames ', 'only output contracts connected to these comma-separated base contract names', validators_1.validateNames) - .addOption(new commander_1.Option('-d, --depth ', 'depth of connected classes to the base contracts. 1 will only show directly connected contracts, interfaces, libraries, structs and enums.').default('100', 'all')) - .option('-c, --clusterFolders', 'cluster contracts into source folders', false) - .option('-hv, --hideVariables', 'hide variables from contracts, interfaces, structs and enums', false) - .option('-hf, --hideFunctions', 'hide functions from contracts, interfaces and libraries', false) - .option('-hp, --hidePrivates', 'hide private and internal attributes and operators', false) - .option('-hm, --hideModifiers', 'hide modifier functions from contracts', false) - .option('-ht, --hideEvents', 'hide events from contracts, interfaces and libraries', false) + .option( + '-b, --baseContractNames ', + 'only output contracts connected to these comma-separated base contract names', + validators_1.validateNames, + ) + .addOption( + new commander_1.Option( + '-d, --depth ', + 'depth of connected classes to the base contracts. 1 will only show directly connected contracts, interfaces, libraries, structs and enums.', + ).default('100', 'all'), + ) + .option( + '-c, --clusterFolders', + 'cluster contracts into source folders', + false, + ) + .option( + '-hv, --hideVariables', + 'hide variables from contracts, interfaces, structs and enums', + false, + ) + .option( + '-hf, --hideFunctions', + 'hide functions from contracts, interfaces and libraries', + false, + ) + .option( + '-hp, --hidePrivates', + 'hide private and internal attributes and operators', + false, + ) + .option( + '-hm, --hideModifiers', + 'hide modifier functions from contracts', + false, + ) + .option( + '-ht, --hideEvents', + 'hide events from contracts, interfaces and libraries', + false, + ) .option('-hc, --hideConstants', 'hide file level constants', false) .option('-hx, --hideContracts', 'hide contracts', false) .option('-he, --hideEnums', 'hide enum types', false) @@ -72,209 +149,408 @@ program .option('-hi, --hideInterfaces', 'hide interfaces', false) .option('-ha, --hideAbstracts', 'hide abstract contracts', false) .option('-hn, --hideFilename', 'hide relative path and file name', false) - .option('-s, --squash', 'squash inherited contracts to the base contract(s)', false) - .option('-hsc, --hideSourceContract', 'hide the source contract when using squash', false) + .option( + '-s, --squash', + 'squash inherited contracts to the base contract(s)', + false, + ) + .option( + '-hsc, --hideSourceContract', + 'hide the source contract when using squash', + false, + ) .action(async (fileFolderAddress, options, command) => { - try { - const combinedOptions = { - ...command.parent._optionValues, - ...options, - }; - // Parse Solidity code from local file system or verified source code on Etherscan. - let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions); - if (options.squash && - // Must specify base contract(s) or parse from Etherscan to get contractName - !options.baseContractNames && - !contractName) { - throw Error('Must specify base contract(s) when using the squash option against local Solidity files.'); - } - if (options.squash && options.hideContracts) { - throw Error('Can not hide contracts when squashing contracts.'); - } - if (options.baseContractNames) { - contractName = options.baseContractNames[0]; - } - // Filter out any class stereotypes that are to be hidden - let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)(umlClasses, options); - // squash contracts - if (options.squash) { - filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)(filteredUmlClasses, options.baseContractNames || [contractName]); - } - if (options.baseContractNames || options.squash) { - // Find all the classes connected to the base classes after they have been squashed - filteredUmlClasses = (0, filterClasses_1.classesConnectedToBaseContracts)(filteredUmlClasses, options.baseContractNames || [contractName], options.depth); + try { + const combinedOptions = { + ...command.parent._optionValues, + ...options, + } + // Parse Solidity code from local file system or verified source code on Etherscan. + let { umlClasses, contractName } = await (0, + parserGeneral_1.parserUmlClasses)( + fileFolderAddress, + combinedOptions, + ) + if ( + options.squash && + // Must specify base contract(s) or parse from Etherscan to get contractName + !options.baseContractNames && + !contractName + ) { + throw Error( + 'Must specify base contract(s) when using the squash option against local Solidity files.', + ) + } + if (options.squash && options.hideContracts) { + throw Error('Can not hide contracts when squashing contracts.') + } + if (options.baseContractNames) { + contractName = options.baseContractNames[0] + } + // Filter out any class stereotypes that are to be hidden + let filteredUmlClasses = (0, filterClasses_1.filterHiddenClasses)( + umlClasses, + options, + ) + // squash contracts + if (options.squash) { + filteredUmlClasses = (0, squashClasses_1.squashUmlClasses)( + filteredUmlClasses, + options.baseContractNames || [contractName], + ) + } + if (options.baseContractNames || options.squash) { + // Find all the classes connected to the base classes after they have been squashed + filteredUmlClasses = (0, + filterClasses_1.classesConnectedToBaseContracts)( + filteredUmlClasses, + options.baseContractNames || [contractName], + options.depth, + ) + } + // Convert UML classes to Graphviz dot format. + const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)( + filteredUmlClasses, + combinedOptions.clusterFolders, + combinedOptions, + ) + // Convert Graphviz dot format to file formats. eg svg or png + await (0, writerFiles_1.writeOutputFiles)( + dotString, + contractName || 'classDiagram', + combinedOptions.outputFormat, + combinedOptions.outputFileName, + ) + debug('Finished generating UML') + } catch (err) { + console.error(err) + process.exit(2) } - // Convert UML classes to Graphviz dot format. - const dotString = (0, converterClasses2Dot_1.convertUmlClasses2Dot)(filteredUmlClasses, combinedOptions.clusterFolders, combinedOptions); - // Convert Graphviz dot format to file formats. eg svg or png - await (0, writerFiles_1.writeOutputFiles)(dotString, contractName || 'classDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName); - debug(`Finished generating UML`); - } - catch (err) { - console.error(err); - process.exit(2); - } -}); + }) program .command('storage') .usage('[options] ') - .description(`Visually display a contract's storage slots. + .description( + `Visually display a contract's storage slots. -WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.\n`) +WARNING: sol2uml does not use the Solidity compiler so may differ with solc. A known example is fixed-sized arrays declared with an expression will fail to be sized.\n`, + ) .argument('fileFolderAddress', argumentText) - .option('-c, --contract ', 'Contract name in the local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.') - .option('-cf, --contractFile ', 'Filename the contract is located in. This can include the relative path to the desired file.') - .option('-d, --data', 'Gets the values in the storage slots from an Ethereum node.', false) - .option('-s, --storage
', 'The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.', validators_1.validateAddress) - .addOption(new commander_1.Option('-u, --url ', 'URL of the Ethereum node to get storage values if the `data` option is used.') - .env('NODE_URL') - .default('http://localhost:8545')) - .option('-bn, --block ', 'Block number to get the contract storage values from.', 'latest') - .option('-sn, --slotNames ', 'Comma-separated list of slot names when accessed by assembly. The names can be a string, which will be hashed to a slot, or a 32 bytes hexadecimal string with a 0x prefix.', validators_1.validateSlotNames) - .option('-st, --slotTypes ', 'Comma-separated list of types for the slots listed in the `slotNames` option. eg address,uint256,bool. If all types are the same, a single type can be used. eg address', validators_1.validateTypes, ['bytes32']) - .option('-a, --array ', 'Number of slots to display at the start and end of arrays.', '2') - .option('-hx, --hideExpand ', "Comma-separated list of storage variables to not expand. That's arrays, structs, strings or bytes.", validators_1.validateNames) + .option( + '-c, --contract ', + 'Contract name in the local Solidity files. Not needed when using an address as the first argument as the contract name can be derived from Etherscan.', + ) + .option( + '-cf, --contractFile ', + 'Filename the contract is located in. This can include the relative path to the desired file.', + ) + .option( + '-d, --data', + 'Gets the values in the storage slots from an Ethereum node.', + false, + ) + .option( + '-s, --storage
', + 'The address of the contract with the storage values. This will be different from the contract with the code if a proxy contract is used. This is not needed if `fileFolderAddress` is an address and the contract is not proxied.', + validators_1.validateAddress, + ) + .addOption( + new commander_1.Option( + '-u, --url ', + 'URL of the Ethereum node to get storage values if the `data` option is used.', + ) + .env('NODE_URL') + .default('http://localhost:8545'), + ) + .option( + '-bn, --block ', + 'Block number to get the contract storage values from.', + 'latest', + ) + .option( + '-sn, --slotNames ', + 'Comma-separated list of slot names when accessed by assembly. The names can be a string, which will be hashed to a slot, or a 32 bytes hexadecimal string with a 0x prefix.', + validators_1.validateSlotNames, + ) + .option( + '-st, --slotTypes ', + 'Comma-separated list of types for the slots listed in the `slotNames` option. eg address,uint256,bool. If all types are the same, a single type can be used. eg address', + validators_1.validateTypes, + ['bytes32'], + ) + .option( + '-a, --array ', + 'Number of slots to display at the start and end of arrays.', + '2', + ) + .option( + '-hx, --hideExpand ', + "Comma-separated list of storage variables to not expand. That's arrays, structs, strings or bytes.", + validators_1.validateNames, + ) .option('-hv, --hideValues', 'Hide storage slot value column.', false) .action(async (fileFolderAddress, options, command) => { - try { - const combinedOptions = { - ...command.parent._optionValues, - ...options, - }; - // If not an address and the contractName option has not been specified - if (!(0, regEx_1.isAddress)(fileFolderAddress) && !combinedOptions.contract) { - throw Error(`Must use the \`-c, --contract \` option to specify the contract to draw the storage diagram for when sourcing from local files.\nThis option is not needed when sourcing from a blockchain explorer with a contract address.`); - } - let { umlClasses, contractName } = await (0, parserGeneral_1.parserUmlClasses)(fileFolderAddress, combinedOptions); - contractName = combinedOptions.contract || contractName; - const arrayItems = parseInt(combinedOptions.array); - const storageSections = (0, converterClasses2Storage_1.convertClasses2StorageSections)(contractName, umlClasses, arrayItems, combinedOptions.contractFile, options.hideExpand); - const optionVariables = (0, converterClasses2Storage_1.optionStorageVariables)(contractName, options.slotNames, options.slotTypes); - storageSections[0].variables = [ - ...storageSections[0].variables, - ...optionVariables, - ]; - if ((0, regEx_1.isAddress)(fileFolderAddress)) { - // The first storage is the contract - storageSections[0].address = fileFolderAddress; - } - if (combinedOptions.data) { - let storageAddress = combinedOptions.storage; - if (storageAddress) { - if (!(0, regEx_1.isAddress)(storageAddress)) { - throw Error(`Invalid address to get storage data from "${storageAddress}"`); - } + try { + const combinedOptions = { + ...command.parent._optionValues, + ...options, } - else { - if (!(0, regEx_1.isAddress)(fileFolderAddress)) { - throw Error(`Can not get storage slot values if first param is not an address and the \`--storage\` option is not used.`); - } - storageAddress = fileFolderAddress; + // If not an address and the contractName option has not been specified + if ( + !(0, regEx_1.isAddress)(fileFolderAddress) && + !combinedOptions.contract + ) { + throw Error( + 'Must use the `-c, --contract ` option to specify the contract to draw the storage diagram for when sourcing from local files.\nThis option is not needed when sourcing from a blockchain explorer with a contract address.', + ) } - let block = await (0, block_1.getBlock)(combinedOptions); - // Get slot values for each storage section - for (const storageSection of storageSections) { - await (0, slotValues_1.addSlotValues)(combinedOptions.url, storageAddress, storageSection, arrayItems, block); - // Add storage variables for dynamic arrays, strings and bytes - await (0, converterClasses2Storage_1.addDynamicVariables)(storageSection, storageSections, combinedOptions.url, storageAddress, arrayItems, block); + let { umlClasses, contractName } = await (0, + parserGeneral_1.parserUmlClasses)( + fileFolderAddress, + combinedOptions, + ) + contractName = combinedOptions.contract || contractName + const arrayItems = parseInt(combinedOptions.array) + const storageSections = (0, + converterClasses2Storage_1.convertClasses2StorageSections)( + contractName, + umlClasses, + arrayItems, + combinedOptions.contractFile, + options.hideExpand, + ) + const optionVariables = (0, + converterClasses2Storage_1.optionStorageVariables)( + contractName, + options.slotNames, + options.slotTypes, + ) + storageSections[0].variables = [ + ...storageSections[0].variables, + ...optionVariables, + ] + if ((0, regEx_1.isAddress)(fileFolderAddress)) { + // The first storage is the contract + storageSections[0].address = fileFolderAddress + } + if (combinedOptions.data) { + let storageAddress = combinedOptions.storage + if (storageAddress) { + if (!(0, regEx_1.isAddress)(storageAddress)) { + throw Error( + `Invalid address to get storage data from "${storageAddress}"`, + ) + } + } else { + if (!(0, regEx_1.isAddress)(fileFolderAddress)) { + throw Error( + 'Can not get storage slot values if first param is not an address and the `--storage` option is not used.', + ) + } + storageAddress = fileFolderAddress + } + const block = await (0, block_1.getBlock)(combinedOptions) + // Get slot values for each storage section + for (const storageSection of storageSections) { + await (0, slotValues_1.addSlotValues)( + combinedOptions.url, + storageAddress, + storageSection, + arrayItems, + block, + ) + // Add storage variables for dynamic arrays, strings and bytes + await (0, converterClasses2Storage_1.addDynamicVariables)( + storageSection, + storageSections, + combinedOptions.url, + storageAddress, + arrayItems, + block, + ) + } } + const dotString = (0, converterStorage2Dot_1.convertStorages2Dot)( + storageSections, + combinedOptions, + ) + await (0, writerFiles_1.writeOutputFiles)( + dotString, + contractName || 'storageDiagram', + combinedOptions.outputFormat, + combinedOptions.outputFileName, + ) + } catch (err) { + console.error(err) + process.exit(2) } - const dotString = (0, converterStorage2Dot_1.convertStorages2Dot)(storageSections, combinedOptions); - await (0, writerFiles_1.writeOutputFiles)(dotString, contractName || 'storageDiagram', combinedOptions.outputFormat, combinedOptions.outputFileName); - } - catch (err) { - console.error(err); - process.exit(2); - } -}); + }) program .command('flatten') .usage('') - .description(`Merges verified source files for a contract from a Blockchain explorer into one local Solidity file. + .description( + `Merges verified source files for a contract from a Blockchain explorer into one local Solidity file. In order for the merged code to compile, the following is done: 1. pragma solidity is set using the compiler of the verified contract. 2. All pragma solidity lines in the source files are commented out. 3. File imports are commented out. 4. "SPDX-License-Identifier" is renamed to "SPDX--License-Identifier". -5. Contract dependencies are analysed so the files are merged in an order that will compile.\n`) - .argument('', 'Contract address in hexadecimal format with a 0x prefix.', validators_1.validateAddress) +5. Contract dependencies are analysed so the files are merged in an order that will compile.\n`, + ) + .argument( + '', + 'Contract address in hexadecimal format with a 0x prefix.', + validators_1.validateAddress, + ) .action(async (contractAddress, options, command) => { - try { - debug(`About to flatten ${contractAddress}`); - const combinedOptions = { - ...command.parent._optionValues, - ...options, - }; - const etherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network, combinedOptions.explorerUrl); - const { solidityCode, contractName } = await etherscanParser.getSolidityCode(contractAddress); - // Write Solidity to the contract address - const outputFilename = combinedOptions.outputFileName || contractName; - await (0, writerFiles_1.writeSourceCode)(solidityCode, outputFilename); - } - catch (err) { - console.error(err); - process.exit(2); - } -}); + try { + debug(`About to flatten ${contractAddress}`) + const combinedOptions = { + ...command.parent._optionValues, + ...options, + } + const etherscanParser = new parserEtherscan_1.EtherscanParser( + combinedOptions.apiKey, + combinedOptions.network, + combinedOptions.explorerUrl, + ) + const { solidityCode, contractName } = + await etherscanParser.getSolidityCode(contractAddress) + // Write Solidity to the contract address + const outputFilename = + combinedOptions.outputFileName || contractName + await (0, writerFiles_1.writeSourceCode)( + solidityCode, + outputFilename, + ) + } catch (err) { + console.error(err) + process.exit(2) + } + }) program .command('diff') .usage('[options] ') - .description(`Compare verified contract code on Etherscan-like explorers to another verified contract, a local file or multiple local files. + .description( + `Compare verified contract code on Etherscan-like explorers to another verified contract, a local file or multiple local files. The results show the comparison of contract A to B. The ${clc.green('green')} sections are additions to contract B that are not in contract A. The ${clc.red('red')} sections are removals from contract A that are not in contract B. -The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.\n`) - .argument('', 'Contract address in hexadecimal format with a 0x prefix of the first contract', validators_1.validateAddress) - .argument('', `Location of the contract source code to compare against. Can be a filename, comma-separated list of local folders or a contract address. Examples: +The line numbers are from contract B. There are no line numbers for the red sections as they are not in contract B.\n`, + ) + .argument( + '', + 'Contract address in hexadecimal format with a 0x prefix of the first contract', + validators_1.validateAddress, + ) + .argument( + '', + `Location of the contract source code to compare against. Can be a filename, comma-separated list of local folders or a contract address. Examples: "flat.sol" will compare against a local file called "flat.sol". This must be used when address A's verified source code is a single, flat file. ".,node_modules" will compare against local files under the current working folder and the node_modules folder. This is used when address A's verified source code is multiple files. - 0x1091588Cc431275F99DC5Df311fd8E1Ab81c89F3 will compare against the verified source code from Etherscan.`) - .option('-s, --summary', 'Only show a summary of the file differences', false) - .option('-af --aFile ', 'Limit code compare to contract A source file with the full path and extension as displayed in the file summary (default: compares all source files)') - .option('-bf --bFile ', 'Contract B source file with the full path and extension as displayed in the file summary. Used if aFile is specified and the source file has been renamed (default: aFile if specified)') - .addOption(new commander_1.Option('-bn, --bNetwork ', 'Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option)').choices(parserEtherscan_1.networks)) - .option('-be, --bExplorerUrl ', 'Override the `bNetwork` option with custom blockchain explorer API URL for contract B if on a different blockchain to contract A. Contract A uses the `explorerUrl` (default: value of `explorerUrl` option)') - .option('-bk, --bApiKey ', 'Blockchain explorer API key for contract B if on a different blockchain to contract A. Contract A uses the `apiKey` option (default: value of `apiKey` option)') - .option('--flatten', 'Flatten into a single file before comparing. Only works when comparing two verified contracts, not to local files', false) - .option('--saveFiles', 'Save the flattened contract code to the filesystem when using the `flatten` option. The file names will be the contract address with a .sol extension', false) - .option('-l, --lineBuffer ', 'Minimum number of lines before and after changes (default: 4)', validators_1.validateLineBuffer) + 0x1091588Cc431275F99DC5Df311fd8E1Ab81c89F3 will compare against the verified source code from Etherscan.`, + ) + .option( + '-s, --summary', + 'Only show a summary of the file differences', + false, + ) + .option( + '-af --aFile ', + 'Limit code compare to contract A source file with the full path and extension as displayed in the file summary (default: compares all source files)', + ) + .option( + '-bf --bFile ', + 'Contract B source file with the full path and extension as displayed in the file summary. Used if aFile is specified and the source file has been renamed (default: aFile if specified)', + ) + .addOption( + new commander_1.Option( + '-bn, --bNetwork ', + 'Ethereum network which maps to a blockchain explorer for contract B if on a different blockchain to contract A. Contract A uses the `network` option (default: value of `network` option)', + ).choices(parserEtherscan_1.networks), + ) + .option( + '-be, --bExplorerUrl ', + 'Override the `bNetwork` option with custom blockchain explorer API URL for contract B if on a different blockchain to contract A. Contract A uses the `explorerUrl` (default: value of `explorerUrl` option)', + ) + .option( + '-bk, --bApiKey ', + 'Blockchain explorer API key for contract B if on a different blockchain to contract A. Contract A uses the `apiKey` option (default: value of `apiKey` option)', + ) + .option( + '--flatten', + 'Flatten into a single file before comparing. Only works when comparing two verified contracts, not to local files', + false, + ) + .option( + '--saveFiles', + 'Save the flattened contract code to the filesystem when using the `flatten` option. The file names will be the contract address with a .sol extension', + false, + ) + .option( + '-l, --lineBuffer ', + 'Minimum number of lines before and after changes (default: 4)', + validators_1.validateLineBuffer, + ) .action(async (addressA, fileFoldersAddress, options, command) => { - try { - debug(`About to compare ${addressA} to ${fileFoldersAddress}`); - const combinedOptions = { - ...command.parent._optionValues, - ...options, - }; - const aEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.apiKey, combinedOptions.network, combinedOptions.explorerUrl); - if ((0, regEx_1.isAddress)(fileFoldersAddress)) { - const addressB = fileFoldersAddress; - const bEtherscanParser = new parserEtherscan_1.EtherscanParser(combinedOptions.bApiKey || combinedOptions.apiKey, combinedOptions.bNetwork || combinedOptions.network, combinedOptions.bExplorerUrl || combinedOptions.explorerUrl); - // If flattening - if (options.flatten) { - await (0, diffContracts_1.compareFlattenContracts)(addressA, addressB, aEtherscanParser, bEtherscanParser, combinedOptions); + try { + debug(`About to compare ${addressA} to ${fileFoldersAddress}`) + const combinedOptions = { + ...command.parent._optionValues, + ...options, } - else { - await (0, diffContracts_1.compareVerifiedContracts)(addressA, aEtherscanParser, addressB, bEtherscanParser, combinedOptions); + const aEtherscanParser = new parserEtherscan_1.EtherscanParser( + combinedOptions.apiKey, + combinedOptions.network, + combinedOptions.explorerUrl, + ) + if ((0, regEx_1.isAddress)(fileFoldersAddress)) { + const addressB = fileFoldersAddress + const bEtherscanParser = new parserEtherscan_1.EtherscanParser( + combinedOptions.bApiKey || combinedOptions.apiKey, + combinedOptions.bNetwork || combinedOptions.network, + combinedOptions.bExplorerUrl || combinedOptions.explorerUrl, + ) + // If flattening + if (options.flatten) { + await (0, diffContracts_1.compareFlattenContracts)( + addressA, + addressB, + aEtherscanParser, + bEtherscanParser, + combinedOptions, + ) + } else { + await (0, diffContracts_1.compareVerifiedContracts)( + addressA, + aEtherscanParser, + addressB, + bEtherscanParser, + combinedOptions, + ) + } + } else { + const localFolders = fileFoldersAddress.split(',') + await (0, diffContracts_1.compareVerified2Local)( + addressA, + aEtherscanParser, + localFolders, + combinedOptions, + ) } + } catch (err) { + console.error(err) + process.exit(2) } - else { - const localFolders = fileFoldersAddress.split(','); - await (0, diffContracts_1.compareVerified2Local)(addressA, aEtherscanParser, localFolders, combinedOptions); - } - } - catch (err) { - console.error(err); - process.exit(2); - } -}); + }) program.on('option:verbose', () => { - debugControl.enable('sol2uml,axios'); - debug('verbose on'); -}); + debugControl.enable('sol2uml,axios') + debug('verbose on') +}) const main = async () => { - await program.parseAsync(process.argv); -}; -main(); -//# sourceMappingURL=sol2uml.js.map \ No newline at end of file + await program.parseAsync(process.argv) +} +main() +// # sourceMappingURL=sol2uml.js.map diff --git a/lib/squashClasses.d.ts b/lib/squashClasses.d.ts index 4eff7cca..5185ec20 100644 --- a/lib/squashClasses.d.ts +++ b/lib/squashClasses.d.ts @@ -1,8 +1,11 @@ -import { UmlClass } from './umlClass'; +import { UmlClass } from './umlClass' /** * Flattens the inheritance hierarchy for each base contract. * @param umlClasses array of UML classes of type `UMLClass`. The new squashed class is added to this array. * @param baseContractNames array of contract names to be rendered in squashed format. * @return squashUmlClasses array of UML classes of type `UMLClass` that are to be rendered */ -export declare const squashUmlClasses: (umlClasses: UmlClass[], baseContractNames: readonly string[]) => UmlClass[]; +export declare const squashUmlClasses: ( + umlClasses: UmlClass[], + baseContractNames: readonly string[], +) => UmlClass[] diff --git a/lib/squashClasses.js b/lib/squashClasses.js index d97ed50f..5b0b1628 100644 --- a/lib/squashClasses.js +++ b/lib/squashClasses.js @@ -1,32 +1,64 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; +'use strict' +const __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k + let desc = Object.getOwnPropertyDescriptor(m, k) + if ( + !desc || + ('get' in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k] + }, + } + } + Object.defineProperty(o, k2, desc) + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k + o[k2] = m[k] + }) +const __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { + enumerable: true, + value: v, + }) + } + : function (o, v) { + o.default = v + }) +const __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod + const result = {} + if (mod != null) { + for (const k in mod) { + if ( + k !== 'default' && + Object.prototype.hasOwnProperty.call(mod, k) + ) { + __createBinding(result, mod, k) + } + } + } + __setModuleDefault(result, mod) + return result } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.squashUmlClasses = void 0; -const umlClass_1 = require("./umlClass"); -const crypto = __importStar(require("crypto")); -const debug = require('debug')('sol2uml'); +Object.defineProperty(exports, '__esModule', { value: true }) +exports.squashUmlClasses = void 0 +const umlClass_1 = require('./umlClass') +const crypto = __importStar(require('crypto')) +const debug = require('debug')('sol2uml') /** * Flattens the inheritance hierarchy for each base contract. * @param umlClasses array of UML classes of type `UMLClass`. The new squashed class is added to this array. @@ -34,116 +66,154 @@ const debug = require('debug')('sol2uml'); * @return squashUmlClasses array of UML classes of type `UMLClass` that are to be rendered */ const squashUmlClasses = (umlClasses, baseContractNames) => { - let removedClassIds = []; + let removedClassIds = [] for (const baseContractName of baseContractNames) { // Find the base UML Class to squash - let baseIndex = umlClasses.findIndex(({ name }) => { - return name === baseContractName; - }); + const baseIndex = umlClasses.findIndex(({ name }) => { + return name === baseContractName + }) if (baseIndex === undefined) { - throw Error(`Failed to find contract with name "${baseContractName}" to squash`); + throw Error( + `Failed to find contract with name "${baseContractName}" to squash`, + ) } - const baseClass = umlClasses[baseIndex]; - let squashedClass = new umlClass_1.UmlClass({ + const baseClass = umlClasses[baseIndex] + const squashedClass = new umlClass_1.UmlClass({ name: baseClass.name, absolutePath: baseClass.absolutePath, relativePath: baseClass.relativePath, - }); - squashedClass.id = baseClass.id; - const result = recursiveSquash(squashedClass, [], baseClass, umlClasses, 1); - removedClassIds = removedClassIds.concat(result.removedClassIds); + }) + squashedClass.id = baseClass.id + const result = recursiveSquash( + squashedClass, + [], + baseClass, + umlClasses, + 1, + ) + removedClassIds = removedClassIds.concat(result.removedClassIds) // Remove overridden functions from squashed class - squashedClass.operators = reduceOperators(squashedClass.operators); - umlClasses[baseIndex] = squashedClass; + squashedClass.operators = reduceOperators(squashedClass.operators) + umlClasses[baseIndex] = squashedClass } // filter the list of classes that will be rendered - return umlClasses.filter((u) => - // remove any squashed inherited contracts - !removedClassIds.includes(u.id) || - // Include all base contracts - baseContractNames.includes(u.name)); -}; -exports.squashUmlClasses = squashUmlClasses; -const recursiveSquash = (squashedClass, inheritedContractNames, baseClass, umlClasses, startPosition) => { - let currentPosition = startPosition; - const removedClassIds = []; + return umlClasses.filter( + (u) => + // remove any squashed inherited contracts + !removedClassIds.includes(u.id) || + // Include all base contracts + baseContractNames.includes(u.name), + ) +} +exports.squashUmlClasses = squashUmlClasses +const recursiveSquash = ( + squashedClass, + inheritedContractNames, + baseClass, + umlClasses, + startPosition, +) => { + let currentPosition = startPosition + const removedClassIds = [] // For each association from the baseClass - for (const [targetClassName, association] of Object.entries(baseClass.associations)) { + for (const [targetClassName, association] of Object.entries( + baseClass.associations, + )) { // if inheritance and (Abstract or Contract) // Libraries and Interfaces will be copied if (association.realization) { // Find the target UML Class const inheritedContract = umlClasses.find(({ name }) => { - return name === targetClassName; - }); + return name === targetClassName + }) if (!inheritedContract) { - debug(`Warning: failed to find inherited contract with name ${targetClassName}`); - continue; + debug( + `Warning: failed to find inherited contract with name ${targetClassName}`, + ) + continue } // Is the associated class a contract or abstract contract? - if (inheritedContract?.stereotype === umlClass_1.ClassStereotype.Library) { - squashedClass.addAssociation(association); - } - else { + if ( + inheritedContract?.stereotype === + umlClass_1.ClassStereotype.Library + ) { + squashedClass.addAssociation(association) + } else { // has the contract already been added to the inheritance tree? - const alreadyInherited = inheritedContractNames.includes(inheritedContract.name); + const alreadyInherited = inheritedContractNames.includes( + inheritedContract.name, + ) // Do not add inherited contract if it has already been added to the inheritance tree if (!alreadyInherited) { - inheritedContractNames.push(inheritedContract.name); - const squashResult = recursiveSquash(squashedClass, inheritedContractNames, inheritedContract, umlClasses, currentPosition++); + inheritedContractNames.push(inheritedContract.name) + const squashResult = recursiveSquash( + squashedClass, + inheritedContractNames, + inheritedContract, + umlClasses, + currentPosition++, + ) // Add to list of removed class ids - removedClassIds.push(...squashResult.removedClassIds, inheritedContract.id); + removedClassIds.push( + ...squashResult.removedClassIds, + inheritedContract.id, + ) } } - } - else { + } else { // Copy association but will not duplicate it - squashedClass.addAssociation(association); + squashedClass.addAssociation(association) } } // Copy class properties from the baseClass to the squashedClass - baseClass.constants.forEach((c) => squashedClass.constants.push({ ...c, sourceContract: baseClass.name })); - baseClass.attributes.forEach((a) => squashedClass.attributes.push({ ...a, sourceContract: baseClass.name })); - baseClass.enums.forEach((e) => squashedClass.enums.push(e)); - baseClass.structs.forEach((s) => squashedClass.structs.push(s)); - baseClass.imports.forEach((i) => squashedClass.imports.push(i)); + baseClass.constants.forEach((c) => + squashedClass.constants.push({ ...c, sourceContract: baseClass.name }), + ) + baseClass.attributes.forEach((a) => + squashedClass.attributes.push({ ...a, sourceContract: baseClass.name }), + ) + baseClass.enums.forEach((e) => squashedClass.enums.push(e)) + baseClass.structs.forEach((s) => squashedClass.structs.push(s)) + baseClass.imports.forEach((i) => squashedClass.imports.push(i)) // copy the functions - baseClass.operators.forEach((f) => squashedClass.operators.push({ - ...f, - hash: hash(f), - inheritancePosition: currentPosition, - sourceContract: baseClass.name, - })); + baseClass.operators.forEach((f) => + squashedClass.operators.push({ + ...f, + hash: hash(f), + inheritancePosition: currentPosition, + sourceContract: baseClass.name, + }), + ) return { currentPosition, removedClassIds, - }; -}; + } +} const hash = (operator) => { - const hash = crypto.createHash('sha256'); - let data = operator.name ?? 'fallback'; + const hash = crypto.createHash('sha256') + let data = operator.name ?? 'fallback' operator.parameters?.forEach((p) => { - data += ',' + p.type; - }); + data += ',' + p.type + }) operator.returnParameters?.forEach((p) => { - data += ',' + p.type; - }); - return hash.update(data).digest('hex'); -}; + data += ',' + p.type + }) + return hash.update(data).digest('hex') +} const reduceOperators = (operators) => { - const hashes = new Set(operators.map((o) => o.hash)); - const operatorsWithNoHash = operators.filter((o) => !o.hash); - const newOperators = []; + const hashes = new Set(operators.map((o) => o.hash)) + const operatorsWithNoHash = operators.filter((o) => !o.hash) + const newOperators = [] for (const hash of hashes) { const operator = operators .filter((o) => o.hash === hash) // sort operators by inheritance position. smaller to highest .sort((o) => o.inheritancePosition) // get last operator in the array - .slice(-1)[0]; - newOperators.push(operator); + .slice(-1)[0] + newOperators.push(operator) } - newOperators.push(...operatorsWithNoHash); - return newOperators; -}; -//# sourceMappingURL=squashClasses.js.map \ No newline at end of file + newOperators.push(...operatorsWithNoHash) + return newOperators +} +// # sourceMappingURL=squashClasses.js.map diff --git a/lib/typeGuards.d.ts b/lib/typeGuards.d.ts index f6f07810..fbfc8e49 100644 --- a/lib/typeGuards.d.ts +++ b/lib/typeGuards.d.ts @@ -1,8 +1,31 @@ -import { BaseASTNode, EnumDefinition, EventDefinition, FunctionDefinition, ModifierDefinition, StateVariableDeclaration, StructDefinition, UsingForDeclaration } from '@solidity-parser/parser/dist/src/ast-types'; -export declare const isStateVariableDeclaration: (node: BaseASTNode) => node is StateVariableDeclaration; -export declare const isUsingForDeclaration: (node: BaseASTNode) => node is UsingForDeclaration; -export declare const isFunctionDefinition: (node: BaseASTNode) => node is FunctionDefinition; -export declare const isModifierDefinition: (node: BaseASTNode) => node is ModifierDefinition; -export declare const isEventDefinition: (node: BaseASTNode) => node is EventDefinition; -export declare const isStructDefinition: (node: BaseASTNode) => node is StructDefinition; -export declare const isEnumDefinition: (node: BaseASTNode) => node is EnumDefinition; +import { + BaseASTNode, + EnumDefinition, + EventDefinition, + FunctionDefinition, + ModifierDefinition, + StateVariableDeclaration, + StructDefinition, + UsingForDeclaration, +} from '@solidity-parser/parser/dist/src/ast-types' +export declare const isStateVariableDeclaration: ( + node: BaseASTNode, +) => node is StateVariableDeclaration +export declare const isUsingForDeclaration: ( + node: BaseASTNode, +) => node is UsingForDeclaration +export declare const isFunctionDefinition: ( + node: BaseASTNode, +) => node is FunctionDefinition +export declare const isModifierDefinition: ( + node: BaseASTNode, +) => node is ModifierDefinition +export declare const isEventDefinition: ( + node: BaseASTNode, +) => node is EventDefinition +export declare const isStructDefinition: ( + node: BaseASTNode, +) => node is StructDefinition +export declare const isEnumDefinition: ( + node: BaseASTNode, +) => node is EnumDefinition diff --git a/lib/typeGuards.js b/lib/typeGuards.js index 643aac0e..8866f1d4 100644 --- a/lib/typeGuards.js +++ b/lib/typeGuards.js @@ -1,32 +1,39 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.isEnumDefinition = exports.isStructDefinition = exports.isEventDefinition = exports.isModifierDefinition = exports.isFunctionDefinition = exports.isUsingForDeclaration = exports.isStateVariableDeclaration = void 0; +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.isEnumDefinition = + exports.isStructDefinition = + exports.isEventDefinition = + exports.isModifierDefinition = + exports.isFunctionDefinition = + exports.isUsingForDeclaration = + exports.isStateVariableDeclaration = + void 0 const isStateVariableDeclaration = (node) => { - return node.type === 'StateVariableDeclaration'; -}; -exports.isStateVariableDeclaration = isStateVariableDeclaration; + return node.type === 'StateVariableDeclaration' +} +exports.isStateVariableDeclaration = isStateVariableDeclaration const isUsingForDeclaration = (node) => { - return node.type === 'UsingForDeclaration'; -}; -exports.isUsingForDeclaration = isUsingForDeclaration; + return node.type === 'UsingForDeclaration' +} +exports.isUsingForDeclaration = isUsingForDeclaration const isFunctionDefinition = (node) => { - return node.type === 'FunctionDefinition'; -}; -exports.isFunctionDefinition = isFunctionDefinition; + return node.type === 'FunctionDefinition' +} +exports.isFunctionDefinition = isFunctionDefinition const isModifierDefinition = (node) => { - return node.type === 'ModifierDefinition'; -}; -exports.isModifierDefinition = isModifierDefinition; + return node.type === 'ModifierDefinition' +} +exports.isModifierDefinition = isModifierDefinition const isEventDefinition = (node) => { - return node.type === 'EventDefinition'; -}; -exports.isEventDefinition = isEventDefinition; + return node.type === 'EventDefinition' +} +exports.isEventDefinition = isEventDefinition const isStructDefinition = (node) => { - return node.type === 'StructDefinition'; -}; -exports.isStructDefinition = isStructDefinition; + return node.type === 'StructDefinition' +} +exports.isStructDefinition = isStructDefinition const isEnumDefinition = (node) => { - return node.type === 'EnumDefinition'; -}; -exports.isEnumDefinition = isEnumDefinition; -//# sourceMappingURL=typeGuards.js.map \ No newline at end of file + return node.type === 'EnumDefinition' +} +exports.isEnumDefinition = isEnumDefinition +// # sourceMappingURL=typeGuards.js.map diff --git a/lib/umlClass.d.ts b/lib/umlClass.d.ts index 8580c30f..fbea7519 100644 --- a/lib/umlClass.d.ts +++ b/lib/umlClass.d.ts @@ -3,7 +3,7 @@ export declare enum Visibility { Public = 1, External = 2, Internal = 3, - Private = 4 + Private = 4, } export declare enum ClassStereotype { None = 0, @@ -14,7 +14,7 @@ export declare enum ClassStereotype { Struct = 5, Enum = 6, Constant = 7, - Import = 8 + Import = 8, } export declare enum OperatorStereotype { None = 0, @@ -22,97 +22,97 @@ export declare enum OperatorStereotype { Event = 2, Payable = 3, Fallback = 4, - Abstract = 5 + Abstract = 5, } export declare enum AttributeType { Elementary = 0, UserDefined = 1, Function = 2, Array = 3, - Mapping = 4 + Mapping = 4, } export interface Import { - absolutePath: string; + absolutePath: string classNames: { - className: string; - alias?: string; - }[]; + className: string + alias?: string + }[] } export interface Attribute { - visibility?: Visibility; - name: string; - type?: string; - attributeType?: AttributeType; - compiled?: boolean; - sourceContract?: string; + visibility?: Visibility + name: string + type?: string + attributeType?: AttributeType + compiled?: boolean + sourceContract?: string } export interface Parameter { - name?: string; - type: string; + name?: string + type: string } export interface Operator extends Attribute { - stereotype?: OperatorStereotype; - parameters?: Parameter[]; - returnParameters?: Parameter[]; - stateMutability?: string; - modifiers?: string[]; - hash?: string; - inheritancePosition?: number; - sourceContract?: string; + stereotype?: OperatorStereotype + parameters?: Parameter[] + returnParameters?: Parameter[] + stateMutability?: string + modifiers?: string[] + hash?: string + inheritancePosition?: number + sourceContract?: string } export declare enum ReferenceType { Memory = 0, - Storage = 1 + Storage = 1, } export interface Association { - referenceType: ReferenceType; - parentUmlClassName?: string; - targetUmlClassName: string; - realization?: boolean; + referenceType: ReferenceType + parentUmlClassName?: string + targetUmlClassName: string + realization?: boolean } export interface Constants { - name: string; - value: number; - sourceContract?: string; + name: string + value: number + sourceContract?: string } export interface ClassProperties { - name: string; - absolutePath: string; - relativePath: string; - parentId?: number; - importedFileNames?: string[]; - stereotype?: ClassStereotype; - enums?: number[]; - structs?: number[]; - attributes?: Attribute[]; - operators?: Operator[]; + name: string + absolutePath: string + relativePath: string + parentId?: number + importedFileNames?: string[] + stereotype?: ClassStereotype + enums?: number[] + structs?: number[] + attributes?: Attribute[] + operators?: Operator[] associations?: { - [name: string]: Association; - }; - constants?: Constants[]; + [name: string]: Association + } + constants?: Constants[] } export declare class UmlClass implements ClassProperties { - static idCounter: number; - id: number; - name: string; - absolutePath: string; - relativePath: string; - parentId?: number; - imports: Import[]; - stereotype?: ClassStereotype; - constants: Constants[]; - attributes: Attribute[]; - operators: Operator[]; - enums: number[]; - structs: number[]; + static idCounter: number + id: number + name: string + absolutePath: string + relativePath: string + parentId?: number + imports: Import[] + stereotype?: ClassStereotype + constants: Constants[] + attributes: Attribute[] + operators: Operator[] + enums: number[] + structs: number[] associations: { - [name: string]: Association; - }; - constructor(properties: ClassProperties); - addAssociation(association: Association): void; + [name: string]: Association + } + constructor(properties: ClassProperties) + addAssociation(association: Association): void /** * Gets the immediate parent contracts this class inherits from. * Does not include any grand parent associations. That has to be done recursively. */ - getParentContracts(): Association[]; + getParentContracts(): Association[] } diff --git a/lib/umlClass.js b/lib/umlClass.js index e5b7b77f..c6c06668 100644 --- a/lib/umlClass.js +++ b/lib/umlClass.js @@ -1,88 +1,104 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.UmlClass = exports.ReferenceType = exports.AttributeType = exports.OperatorStereotype = exports.ClassStereotype = exports.Visibility = void 0; -var Visibility; -(function (Visibility) { - Visibility[Visibility["None"] = 0] = "None"; - Visibility[Visibility["Public"] = 1] = "Public"; - Visibility[Visibility["External"] = 2] = "External"; - Visibility[Visibility["Internal"] = 3] = "Internal"; - Visibility[Visibility["Private"] = 4] = "Private"; -})(Visibility || (exports.Visibility = Visibility = {})); -var ClassStereotype; -(function (ClassStereotype) { - ClassStereotype[ClassStereotype["None"] = 0] = "None"; - ClassStereotype[ClassStereotype["Library"] = 1] = "Library"; - ClassStereotype[ClassStereotype["Interface"] = 2] = "Interface"; - ClassStereotype[ClassStereotype["Abstract"] = 3] = "Abstract"; - ClassStereotype[ClassStereotype["Contract"] = 4] = "Contract"; - ClassStereotype[ClassStereotype["Struct"] = 5] = "Struct"; - ClassStereotype[ClassStereotype["Enum"] = 6] = "Enum"; - ClassStereotype[ClassStereotype["Constant"] = 7] = "Constant"; - ClassStereotype[ClassStereotype["Import"] = 8] = "Import"; -})(ClassStereotype || (exports.ClassStereotype = ClassStereotype = {})); -var OperatorStereotype; -(function (OperatorStereotype) { - OperatorStereotype[OperatorStereotype["None"] = 0] = "None"; - OperatorStereotype[OperatorStereotype["Modifier"] = 1] = "Modifier"; - OperatorStereotype[OperatorStereotype["Event"] = 2] = "Event"; - OperatorStereotype[OperatorStereotype["Payable"] = 3] = "Payable"; - OperatorStereotype[OperatorStereotype["Fallback"] = 4] = "Fallback"; - OperatorStereotype[OperatorStereotype["Abstract"] = 5] = "Abstract"; -})(OperatorStereotype || (exports.OperatorStereotype = OperatorStereotype = {})); -var AttributeType; -(function (AttributeType) { - AttributeType[AttributeType["Elementary"] = 0] = "Elementary"; - AttributeType[AttributeType["UserDefined"] = 1] = "UserDefined"; - AttributeType[AttributeType["Function"] = 2] = "Function"; - AttributeType[AttributeType["Array"] = 3] = "Array"; - AttributeType[AttributeType["Mapping"] = 4] = "Mapping"; -})(AttributeType || (exports.AttributeType = AttributeType = {})); -var ReferenceType; -(function (ReferenceType) { - ReferenceType[ReferenceType["Memory"] = 0] = "Memory"; - ReferenceType[ReferenceType["Storage"] = 1] = "Storage"; -})(ReferenceType || (exports.ReferenceType = ReferenceType = {})); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.UmlClass = + exports.ReferenceType = + exports.AttributeType = + exports.OperatorStereotype = + exports.ClassStereotype = + exports.Visibility = + void 0 +let Visibility +;(function (Visibility) { + Visibility[(Visibility.None = 0)] = 'None' + Visibility[(Visibility.Public = 1)] = 'Public' + Visibility[(Visibility.External = 2)] = 'External' + Visibility[(Visibility.Internal = 3)] = 'Internal' + Visibility[(Visibility.Private = 4)] = 'Private' +})(Visibility || (exports.Visibility = Visibility = {})) +let ClassStereotype +;(function (ClassStereotype) { + ClassStereotype[(ClassStereotype.None = 0)] = 'None' + ClassStereotype[(ClassStereotype.Library = 1)] = 'Library' + ClassStereotype[(ClassStereotype.Interface = 2)] = 'Interface' + ClassStereotype[(ClassStereotype.Abstract = 3)] = 'Abstract' + ClassStereotype[(ClassStereotype.Contract = 4)] = 'Contract' + ClassStereotype[(ClassStereotype.Struct = 5)] = 'Struct' + ClassStereotype[(ClassStereotype.Enum = 6)] = 'Enum' + ClassStereotype[(ClassStereotype.Constant = 7)] = 'Constant' + ClassStereotype[(ClassStereotype.Import = 8)] = 'Import' +})(ClassStereotype || (exports.ClassStereotype = ClassStereotype = {})) +let OperatorStereotype +;(function (OperatorStereotype) { + OperatorStereotype[(OperatorStereotype.None = 0)] = 'None' + OperatorStereotype[(OperatorStereotype.Modifier = 1)] = 'Modifier' + OperatorStereotype[(OperatorStereotype.Event = 2)] = 'Event' + OperatorStereotype[(OperatorStereotype.Payable = 3)] = 'Payable' + OperatorStereotype[(OperatorStereotype.Fallback = 4)] = 'Fallback' + OperatorStereotype[(OperatorStereotype.Abstract = 5)] = 'Abstract' +})(OperatorStereotype || (exports.OperatorStereotype = OperatorStereotype = {})) +let AttributeType +;(function (AttributeType) { + AttributeType[(AttributeType.Elementary = 0)] = 'Elementary' + AttributeType[(AttributeType.UserDefined = 1)] = 'UserDefined' + AttributeType[(AttributeType.Function = 2)] = 'Function' + AttributeType[(AttributeType.Array = 3)] = 'Array' + AttributeType[(AttributeType.Mapping = 4)] = 'Mapping' +})(AttributeType || (exports.AttributeType = AttributeType = {})) +let ReferenceType +;(function (ReferenceType) { + ReferenceType[(ReferenceType.Memory = 0)] = 'Memory' + ReferenceType[(ReferenceType.Storage = 1)] = 'Storage' +})(ReferenceType || (exports.ReferenceType = ReferenceType = {})) class UmlClass { constructor(properties) { - this.imports = []; - this.constants = []; - this.attributes = []; - this.operators = []; - this.enums = []; - this.structs = []; - this.associations = {}; + this.imports = [] + this.constants = [] + this.attributes = [] + this.operators = [] + this.enums = [] + this.structs = [] + this.associations = {} if (!properties || !properties.name) { - throw TypeError(`Failed to instantiate UML Class with no name property`); + throw TypeError( + 'Failed to instantiate UML Class with no name property', + ) } - Object.assign(this, properties); + Object.assign(this, properties) // Generate a unique identifier for this UML Class - this.id = UmlClass.idCounter++; + this.id = UmlClass.idCounter++ } + addAssociation(association) { if (!association || !association.targetUmlClassName) { - throw TypeError(`Failed to add association. targetUmlClassName was missing`); + throw TypeError( + 'Failed to add association. targetUmlClassName was missing', + ) } // If association doesn't already exist if (!this.associations[association.targetUmlClassName]) { - this.associations[association.targetUmlClassName] = association; + this.associations[association.targetUmlClassName] = association } // associate already exists else { // If new attribute reference type is Storage if (association.referenceType === ReferenceType.Storage) { - this.associations[association.targetUmlClassName].referenceType = ReferenceType.Storage; + this.associations[ + association.targetUmlClassName + ].referenceType = ReferenceType.Storage } } } + /** * Gets the immediate parent contracts this class inherits from. * Does not include any grand parent associations. That has to be done recursively. */ getParentContracts() { - return Object.values(this.associations).filter((association) => association.realization); + return Object.values(this.associations).filter( + (association) => association.realization, + ) } } -exports.UmlClass = UmlClass; -UmlClass.idCounter = 0; -//# sourceMappingURL=umlClass.js.map \ No newline at end of file +exports.UmlClass = UmlClass +UmlClass.idCounter = 0 +// # sourceMappingURL=umlClass.js.map diff --git a/lib/utils/block.d.ts b/lib/utils/block.d.ts index 9c8fd64e..c498c875 100644 --- a/lib/utils/block.d.ts +++ b/lib/utils/block.d.ts @@ -1,5 +1,5 @@ export declare const getBlock: (options: { - block: string; - url: string; - network: string; -}) => Promise; + block: string + url: string + network: string +}) => Promise diff --git a/lib/utils/block.js b/lib/utils/block.js index a404343d..ea4de3af 100644 --- a/lib/utils/block.js +++ b/lib/utils/block.js @@ -1,29 +1,34 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.getBlock = void 0; -const ethers_1 = require("ethers"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.getBlock = void 0 +const ethers_1 = require('ethers') +const debug = require('debug')('sol2uml') const getBlock = async (options) => { if (options.block === 'latest') { try { - const provider = new ethers_1.ethers.providers.JsonRpcProvider(options.url); - const block = await provider.getBlockNumber(); - debug(`Latest block is ${block}. All storage slot values will be from this block.`); - return block; - } - catch (err) { - const defaultMessage = options.url === 'http://localhost:8545' - ? 'This is the default url. Use the `-u, --url` option or `NODE_URL` environment variable to set the url of your blockchain node.' - : `Check your --url option or NODE_URL environment variable is pointing to the correct node for the "${options.network}" blockchain.`; - throw Error(`Failed to connect to blockchain node with url ${options.url}.\n${defaultMessage}`); + const provider = new ethers_1.ethers.providers.JsonRpcProvider( + options.url, + ) + const block = await provider.getBlockNumber() + debug( + `Latest block is ${block}. All storage slot values will be from this block.`, + ) + return block + } catch (err) { + const defaultMessage = + options.url === 'http://localhost:8545' + ? 'This is the default url. Use the `-u, --url` option or `NODE_URL` environment variable to set the url of your blockchain node.' + : `Check your --url option or NODE_URL environment variable is pointing to the correct node for the "${options.network}" blockchain.` + throw Error( + `Failed to connect to blockchain node with url ${options.url}.\n${defaultMessage}`, + ) } } try { - return parseInt(options.block); - } - catch (err) { - throw Error(`Invalid block number: ${options.block}`); + return parseInt(options.block) + } catch (err) { + throw Error(`Invalid block number: ${options.block}`) } -}; -exports.getBlock = getBlock; -//# sourceMappingURL=block.js.map \ No newline at end of file +} +exports.getBlock = getBlock +// # sourceMappingURL=block.js.map diff --git a/lib/utils/diff.d.ts b/lib/utils/diff.d.ts index 077c9eb9..714b397b 100644 --- a/lib/utils/diff.d.ts +++ b/lib/utils/diff.d.ts @@ -4,4 +4,8 @@ * @param codeB * @param lineBuff the number of lines to display before and after each change. */ -export declare const diffCode: (codeA: string, codeB: string, lineBuff: number) => void; +export declare const diffCode: ( + codeA: string, + codeB: string, + lineBuff: number, +) => void diff --git a/lib/utils/diff.js b/lib/utils/diff.js index 5e7c76bf..2d2ff5f9 100644 --- a/lib/utils/diff.js +++ b/lib/utils/diff.js @@ -1,32 +1,64 @@ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; +'use strict' +const __createBinding = + (this && this.__createBinding) || + (Object.create + ? function (o, m, k, k2) { + if (k2 === undefined) k2 = k + let desc = Object.getOwnPropertyDescriptor(m, k) + if ( + !desc || + ('get' in desc + ? !m.__esModule + : desc.writable || desc.configurable) + ) { + desc = { + enumerable: true, + get: function () { + return m[k] + }, + } + } + Object.defineProperty(o, k2, desc) + } + : function (o, m, k, k2) { + if (k2 === undefined) k2 = k + o[k2] = m[k] + }) +const __setModuleDefault = + (this && this.__setModuleDefault) || + (Object.create + ? function (o, v) { + Object.defineProperty(o, 'default', { + enumerable: true, + value: v, + }) + } + : function (o, v) { + o.default = v + }) +const __importStar = + (this && this.__importStar) || + function (mod) { + if (mod && mod.__esModule) return mod + const result = {} + if (mod != null) { + for (const k in mod) { + if ( + k !== 'default' && + Object.prototype.hasOwnProperty.call(mod, k) + ) { + __createBinding(result, mod, k) + } + } + } + __setModuleDefault(result, mod) + return result } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.diffCode = void 0; -const diff_match_patch_1 = __importStar(require("diff-match-patch")); -const clc = require('cli-color'); -const SkippedLinesMarker = `\n---`; +Object.defineProperty(exports, '__esModule', { value: true }) +exports.diffCode = void 0 +const diff_match_patch_1 = __importStar(require('diff-match-patch')) +const clc = require('cli-color') +const SkippedLinesMarker = '\n---' /** * Compares code using Google's diff_match_patch and displays the results in the console. * @param codeA @@ -35,13 +67,13 @@ const SkippedLinesMarker = `\n---`; */ const diffCode = (codeA, codeB, lineBuff) => { // @ts-ignore - const dmp = new diff_match_patch_1.default(); - const diff = dmp.diff_main(codeA, codeB); - dmp.diff_cleanupSemantic(diff); - const linesB = countLines(codeB) + 1; - diff_pretty(diff, linesB, lineBuff); -}; -exports.diffCode = diffCode; + const dmp = new diff_match_patch_1.default() + const diff = dmp.diff_main(codeA, codeB) + dmp.diff_cleanupSemantic(diff) + const linesB = countLines(codeB) + 1 + diff_pretty(diff, linesB, lineBuff) +} +exports.diffCode = diffCode /** * Convert a diff array into human-readable for the console * @param {!Array.} diffs Array of diff tuples. @@ -49,70 +81,81 @@ exports.diffCode = diffCode; * @param lineBuff number of a lines to output before and after the change */ const diff_pretty = (diffs, lines, lineBuff = 2) => { - const linePad = lines.toString().length; - let output = ''; - let diffIndex = 0; - let lineCount = 1; - const firstLineNumber = '1'.padStart(linePad) + ' '; + const linePad = lines.toString().length + let output = '' + let diffIndex = 0 + let lineCount = 1 + const firstLineNumber = '1'.padStart(linePad) + ' ' for (const diff of diffs) { - diffIndex++; - const initialLineNumber = diffIndex <= 1 ? firstLineNumber : ''; - const op = diff[0]; // Operation (insert, delete, equal) - const text = diff[1]; // Text of change. + diffIndex++ + const initialLineNumber = diffIndex <= 1 ? firstLineNumber : '' + const op = diff[0] // Operation (insert, delete, equal) + const text = diff[1] // Text of change. switch (op) { case diff_match_patch_1.DIFF_INSERT: // If first diff then we need to add the first line number - const linesInserted = addLineNumbers(text, lineCount, linePad); - output += initialLineNumber + clc.green(linesInserted); - lineCount += countLines(text); - break; + const linesInserted = addLineNumbers(text, lineCount, linePad) + output += initialLineNumber + clc.green(linesInserted) + lineCount += countLines(text) + break case diff_match_patch_1.DIFF_DELETE: // zero start line means blank line numbers are used - const linesDeleted = addLineNumbers(text, 0, linePad); - output += initialLineNumber + clc.red(linesDeleted); - break; + const linesDeleted = addLineNumbers(text, 0, linePad) + output += initialLineNumber + clc.red(linesDeleted) + break case diff_match_patch_1.DIFF_EQUAL: - const eolPositions = findEOLPositions(text); + const eolPositions = findEOLPositions(text) // If no changes yet if (diffIndex <= 1) { - output += lastLines(text, eolPositions, lineBuff, linePad); + output += lastLines(text, eolPositions, lineBuff, linePad) } // if no more changes else if (diffIndex === diffs.length) { - output += firstLines(text, eolPositions, lineBuff, lineCount, linePad); - } - else { + output += firstLines( + text, + eolPositions, + lineBuff, + lineCount, + linePad, + ) + } else { // else the first n lines and last n lines - output += firstAndLastLines(text, eolPositions, lineBuff, lineCount, linePad); + output += firstAndLastLines( + text, + eolPositions, + lineBuff, + lineCount, + linePad, + ) } - lineCount += eolPositions.length; - break; + lineCount += eolPositions.length + break } } - output += '\n'; - console.log(output); -}; + output += '\n' + console.log(output) +} /** * Used when there is no more changes left */ const firstLines = (text, eolPositions, lineBuff, lineStart, linePad) => { - const lines = text.slice(0, eolPositions[lineBuff]); - return addLineNumbers(lines, lineStart, linePad); -}; + const lines = text.slice(0, eolPositions[lineBuff]) + return addLineNumbers(lines, lineStart, linePad) +} /** * Used before the first change */ const lastLines = (text, eolPositions, lineBuff, linePad) => { - const eolFrom = eolPositions.length - (lineBuff + 1); - let lines = text; - let lineCount = 1; + const eolFrom = eolPositions.length - (lineBuff + 1) + let lines = text + let lineCount = 1 if (eolFrom >= 0) { - lines = eolFrom >= 0 ? text.slice(eolPositions[eolFrom] + 1) : text; - lineCount = eolFrom + 2; + lines = eolFrom >= 0 ? text.slice(eolPositions[eolFrom] + 1) : text + lineCount = eolFrom + 2 } - const firstLineNumber = lineCount.toString().padStart(linePad) + ' '; - return firstLineNumber + addLineNumbers(lines, lineCount, linePad); -}; + const firstLineNumber = lineCount.toString().padStart(linePad) + ' ' + return firstLineNumber + addLineNumbers(lines, lineCount, linePad) +} /** * Used between changes to show the lines after the last change and before the next change. * @param text @@ -121,44 +164,50 @@ const lastLines = (text, eolPositions, lineBuff, linePad) => { * @param lineStart * @param linePad */ -const firstAndLastLines = (text, eolPositions, lineBuff, lineStart, linePad) => { +const firstAndLastLines = ( + text, + eolPositions, + lineBuff, + lineStart, + linePad, +) => { if (eolPositions.length <= 2 * lineBuff) { - return addLineNumbers(text, lineStart, linePad); + return addLineNumbers(text, lineStart, linePad) } - const endFirstLines = eolPositions[lineBuff]; - const eolFrom = eolPositions.length - (lineBuff + 1); - const startLastLines = eolPositions[eolFrom]; + const endFirstLines = eolPositions[lineBuff] + const eolFrom = eolPositions.length - (lineBuff + 1) + const startLastLines = eolPositions[eolFrom] if (startLastLines <= endFirstLines) { - return addLineNumbers(text, lineStart, linePad); + return addLineNumbers(text, lineStart, linePad) } // Lines after the previous change - let lines = text.slice(0, endFirstLines); - let output = addLineNumbers(lines, lineStart, linePad); - output += SkippedLinesMarker; + let lines = text.slice(0, endFirstLines) + let output = addLineNumbers(lines, lineStart, linePad) + output += SkippedLinesMarker // Lines before the next change - lines = text.slice(startLastLines); - const lineCount = lineStart + eolFrom; - output += addLineNumbers(lines, lineCount, linePad); - return output; -}; + lines = text.slice(startLastLines) + const lineCount = lineStart + eolFrom + output += addLineNumbers(lines, lineCount, linePad) + return output +} /** * Gets the positions of the end of lines in the string * @param text */ const findEOLPositions = (text) => { - const eolPositions = []; + const eolPositions = [] text.split('').forEach((c, i) => { if (c === '\n') { - eolPositions.push(i); + eolPositions.push(i) } - }); - return eolPositions; -}; + }) + return eolPositions +} /** * Counts the number of carriage returns in a string * @param text */ -const countLines = (text) => (text.match(/\n/g) || '').length; +const countLines = (text) => (text.match(/\n/g) || '').length /** * Adds left padded line numbers to each line. * @param text with the lines of code @@ -166,23 +215,21 @@ const countLines = (text) => (text.match(/\n/g) || '').length; * @param linePad the width of the largest number which may not be in the text */ const addLineNumbers = (text, lineStart, linePad) => { - let lineCount = lineStart; - let textWithLineNumbers = ''; + let lineCount = lineStart + let textWithLineNumbers = '' text.split('').forEach((c, i) => { if (c === '\n') { if (lineStart > 0) { textWithLineNumbers += `\n${(++lineCount) .toString() - .padStart(linePad)} `; - } - else { - textWithLineNumbers += `\n${' '.repeat(linePad)} `; + .padStart(linePad)} ` + } else { + textWithLineNumbers += `\n${' '.repeat(linePad)} ` } + } else { + textWithLineNumbers += c } - else { - textWithLineNumbers += c; - } - }); - return textWithLineNumbers; -}; -//# sourceMappingURL=diff.js.map \ No newline at end of file + }) + return textWithLineNumbers +} +// # sourceMappingURL=diff.js.map diff --git a/lib/utils/formatters.d.ts b/lib/utils/formatters.d.ts index f1cafd2f..ff872ef1 100644 --- a/lib/utils/formatters.d.ts +++ b/lib/utils/formatters.d.ts @@ -1 +1 @@ -export declare const shortBytes32: (bytes32: string) => string; +export declare const shortBytes32: (bytes32: string) => string diff --git a/lib/utils/formatters.js b/lib/utils/formatters.js index c0e1ed46..bb4a9cfb 100644 --- a/lib/utils/formatters.js +++ b/lib/utils/formatters.js @@ -1,12 +1,14 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.shortBytes32 = void 0; +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.shortBytes32 = void 0 const shortBytes32 = (bytes32) => { - if (!bytes32) - return ''; - if (typeof bytes32 !== 'string' || bytes32.length !== 66) - return bytes32; - return bytes32.slice(0, 5) + '..' + bytes32.slice(-3); -}; -exports.shortBytes32 = shortBytes32; -//# sourceMappingURL=formatters.js.map \ No newline at end of file + if (!bytes32) { + return '' + } + if (typeof bytes32 !== 'string' || bytes32.length !== 66) { + return bytes32 + } + return bytes32.slice(0, 5) + '..' + bytes32.slice(-3) +} +exports.shortBytes32 = shortBytes32 +// # sourceMappingURL=formatters.js.map diff --git a/lib/utils/regEx.d.ts b/lib/utils/regEx.d.ts index 794d8e69..c6846956 100644 --- a/lib/utils/regEx.d.ts +++ b/lib/utils/regEx.d.ts @@ -1,6 +1,6 @@ -export declare const ethereumAddress: RegExp; -export declare const ethereumAddresses: RegExp; -export declare const bytes32: RegExp; -export declare const commaSeparatedList: RegExp; -export declare const isAddress: (input: string) => boolean; -export declare const parseSolidityVersion: (compilerVersion: string) => string; +export declare const ethereumAddress: RegExp +export declare const ethereumAddresses: RegExp +export declare const bytes32: RegExp +export declare const commaSeparatedList: RegExp +export declare const isAddress: (input: string) => boolean +export declare const parseSolidityVersion: (compilerVersion: string) => string diff --git a/lib/utils/regEx.js b/lib/utils/regEx.js index 6dbfdc45..e2ea11ee 100644 --- a/lib/utils/regEx.js +++ b/lib/utils/regEx.js @@ -1,22 +1,28 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.parseSolidityVersion = exports.isAddress = exports.commaSeparatedList = exports.bytes32 = exports.ethereumAddresses = exports.ethereumAddress = void 0; -exports.ethereumAddress = /^0x([A-Fa-f0-9]{40})$/; +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.parseSolidityVersion = + exports.isAddress = + exports.commaSeparatedList = + exports.bytes32 = + exports.ethereumAddresses = + exports.ethereumAddress = + void 0 +exports.ethereumAddress = /^0x([A-Fa-f0-9]{40})$/ // comma-separated list of addresses with no whitespace -exports.ethereumAddresses = /^(0x[A-Fa-f0-9]{40},?)+$/; -exports.bytes32 = /^0x([A-Fa-f0-9]{64})$/; +exports.ethereumAddresses = /^(0x[A-Fa-f0-9]{40},?)+$/ +exports.bytes32 = /^0x([A-Fa-f0-9]{64})$/ // comma-separated list of names with no whitespace -exports.commaSeparatedList = /^[^,\s]+(,[^,\s]+)*$/; +exports.commaSeparatedList = /^[^,\s]+(,[^,\s]+)*$/ const isAddress = (input) => { - return input.match(/^0x([A-Fa-f0-9]{40})$/) !== null; -}; -exports.isAddress = isAddress; + return input.match(/^0x([A-Fa-f0-9]{40})$/) !== null +} +exports.isAddress = isAddress const parseSolidityVersion = (compilerVersion) => { - const result = compilerVersion.match(`v(\\d+.\\d+.\\d+)`); + const result = compilerVersion.match('v(\\d+.\\d+.\\d+)') if (result[1]) { - return result[1]; + return result[1] } - throw Error(`Failed to parse compiler version ${compilerVersion}`); -}; -exports.parseSolidityVersion = parseSolidityVersion; -//# sourceMappingURL=regEx.js.map \ No newline at end of file + throw Error(`Failed to parse compiler version ${compilerVersion}`) +} +exports.parseSolidityVersion = parseSolidityVersion +// # sourceMappingURL=regEx.js.map diff --git a/lib/utils/validators.d.ts b/lib/utils/validators.d.ts index 267195c8..de6f49b9 100644 --- a/lib/utils/validators.d.ts +++ b/lib/utils/validators.d.ts @@ -1,8 +1,8 @@ -export declare const validateAddress: (address: string) => string; -export declare const validateNames: (variables: string) => string[]; -export declare const validateLineBuffer: (lineBufferParam: string) => number; +export declare const validateAddress: (address: string) => string +export declare const validateNames: (variables: string) => string[] +export declare const validateLineBuffer: (lineBufferParam: string) => number export declare const validateSlotNames: (slotNames: string) => { - name: string; - offset: string; -}[]; -export declare const validateTypes: (typesString: string) => string[]; + name: string + offset: string +}[] +export declare const validateTypes: (typesString: string) => string[] diff --git a/lib/utils/validators.js b/lib/utils/validators.js index fe263b3d..b0977c62 100644 --- a/lib/utils/validators.js +++ b/lib/utils/validators.js @@ -1,79 +1,99 @@ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.validateTypes = exports.validateSlotNames = exports.validateLineBuffer = exports.validateNames = exports.validateAddress = void 0; -const regEx_1 = require("./regEx"); -const commander_1 = require("commander"); -const utils_1 = require("ethers/lib/utils"); -const converterClasses2Storage_1 = require("../converterClasses2Storage"); -const debug = require('debug')('sol2uml'); +'use strict' +Object.defineProperty(exports, '__esModule', { value: true }) +exports.validateTypes = + exports.validateSlotNames = + exports.validateLineBuffer = + exports.validateNames = + exports.validateAddress = + void 0 +const regEx_1 = require('./regEx') +const commander_1 = require('commander') +const utils_1 = require('ethers/lib/utils') +const converterClasses2Storage_1 = require('../converterClasses2Storage') +const debug = require('debug')('sol2uml') const validateAddress = (address) => { try { - if (typeof address === 'string' && address?.match(regEx_1.ethereumAddress)) - return (0, utils_1.getAddress)(address); - } - catch (err) { } - throw new commander_1.InvalidArgumentError(`Address must be in hexadecimal format with a 0x prefix.`); -}; -exports.validateAddress = validateAddress; + if ( + typeof address === 'string' && + address?.match(regEx_1.ethereumAddress) + ) { + return (0, utils_1.getAddress)(address) + } + } catch (err) {} + throw new commander_1.InvalidArgumentError( + 'Address must be in hexadecimal format with a 0x prefix.', + ) +} +exports.validateAddress = validateAddress // Splits a comma-separated list of names. const validateNames = (variables) => { try { - if (typeof variables === 'string' && - variables.match(regEx_1.commaSeparatedList)) - return variables.split(','); - } - catch (err) { } - throw new commander_1.InvalidArgumentError(`Must be a comma-separate list of names with no white spaces.`); -}; -exports.validateNames = validateNames; + if ( + typeof variables === 'string' && + variables.match(regEx_1.commaSeparatedList) + ) { + return variables.split(',') + } + } catch (err) {} + throw new commander_1.InvalidArgumentError( + 'Must be a comma-separate list of names with no white spaces.', + ) +} +exports.validateNames = validateNames const validateLineBuffer = (lineBufferParam) => { try { - const lineBuffer = parseInt(lineBufferParam, 10); - if (lineBuffer >= 0) - return lineBuffer; - } - catch (err) { } - throw new commander_1.InvalidOptionArgumentError(`Must be a zero or a positive integer.`); -}; -exports.validateLineBuffer = validateLineBuffer; + const lineBuffer = parseInt(lineBufferParam, 10) + if (lineBuffer >= 0) { + return lineBuffer + } + } catch (err) {} + throw new commander_1.InvalidOptionArgumentError( + 'Must be a zero or a positive integer.', + ) +} +exports.validateLineBuffer = validateLineBuffer const validateSlotNames = (slotNames) => { try { - const slots = slotNames.split(','); + const slots = slotNames.split(',') const results = slots.map((slot) => { if (slot.match(regEx_1.bytes32)) { return { name: undefined, offset: slot, - }; + } } - const offset = (0, utils_1.keccak256)((0, utils_1.toUtf8Bytes)(slot)); - debug(`Slot name "${slot}" has hash "${offset}"`); + const offset = (0, utils_1.keccak256)( + (0, utils_1.toUtf8Bytes)(slot), + ) + debug(`Slot name "${slot}" has hash "${offset}"`) return { name: slot, offset, - }; - }); - console.log(results.length); - return results; - } - catch (err) { } - throw new commander_1.InvalidOptionArgumentError(`Must be a comma-separate list of slots with no white spaces.`); -}; -exports.validateSlotNames = validateSlotNames; + } + }) + console.log(results.length) + return results + } catch (err) {} + throw new commander_1.InvalidOptionArgumentError( + 'Must be a comma-separate list of slots with no white spaces.', + ) +} +exports.validateSlotNames = validateSlotNames const validateTypes = (typesString) => { try { if (typeof typesString === 'string') { - const types = typesString.split(','); + const types = typesString.split(',') types.forEach((type) => { if (!(0, converterClasses2Storage_1.isElementary)(type)) { - throw Error(`"${type}" is not an elementary type`); + throw Error(`"${type}" is not an elementary type`) } - }); - return types; + }) + return types } - } - catch (err) { } - throw new commander_1.InvalidArgumentError(`Slot type must be an elementary type which includes dynamic and fixed size arrays. eg address, address[], uint256, int256[2], bytes32, string, bool`); -}; -exports.validateTypes = validateTypes; -//# sourceMappingURL=validators.js.map \ No newline at end of file + } catch (err) {} + throw new commander_1.InvalidArgumentError( + 'Slot type must be an elementary type which includes dynamic and fixed size arrays. eg address, address[], uint256, int256[2], bytes32, string, bool', + ) +} +exports.validateTypes = validateTypes +// # sourceMappingURL=validators.js.map diff --git a/lib/writerFiles.d.ts b/lib/writerFiles.d.ts index 02677f71..265e9e38 100644 --- a/lib/writerFiles.d.ts +++ b/lib/writerFiles.d.ts @@ -1,4 +1,4 @@ -export type OutputFormats = 'svg' | 'png' | 'dot' | 'all'; +export type OutputFormats = 'svg' | 'png' | 'dot' | 'all' /** * Writes output files to the file system based on the provided input and options. * @param dot The input string in DOT format. @@ -6,10 +6,19 @@ export type OutputFormats = 'svg' | 'png' | 'dot' | 'all'; * @param outputFormat The format of the output file. choices: svg, png, dot or all. default: png * @param outputFilename optional filename of the output file. */ -export declare const writeOutputFiles: (dot: string, contractName: string, outputFormat?: OutputFormats, outputFilename?: string) => Promise; -export declare function convertDot2Svg(dot: string): any; -export declare function writeSourceCode(code: string, filename?: string, extension?: string): void; -export declare function writeDot(dot: string, filename: string): void; +export declare const writeOutputFiles: ( + dot: string, + contractName: string, + outputFormat?: OutputFormats, + outputFilename?: string, +) => Promise +export declare function convertDot2Svg(dot: string): any +export declare function writeSourceCode( + code: string, + filename?: string, + extension?: string, +): void +export declare function writeDot(dot: string, filename: string): void /** * Writes an SVG file to the file system. * @param svg The SVG input to be written to the file system. @@ -17,11 +26,15 @@ export declare function writeDot(dot: string, filename: string): void; * @param outputFormats The format of the output file. choices: svg, png, dot or all. default: png * @throws Error - If there is an error writing the SVG file. */ -export declare function writeSVG(svg: any, svgFilename?: string, outputFormats?: OutputFormats): Promise; +export declare function writeSVG( + svg: any, + svgFilename?: string, + outputFormats?: OutputFormats, +): Promise /** * Asynchronously writes a PNG file to the file system from an SVG input. * @param svg - The SVG input to be converted to a PNG file. * @param filename - The desired file name for the PNG file. * @throws Error - If there is an error converting or writing the PNG file. */ -export declare function writePng(svg: any, filename: string): Promise; +export declare function writePng(svg: any, filename: string): Promise diff --git a/lib/writerFiles.js b/lib/writerFiles.js index 4034fd59..8d7afcef 100644 --- a/lib/writerFiles.js +++ b/lib/writerFiles.js @@ -1,14 +1,22 @@ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.writePng = exports.writeSVG = exports.writeDot = exports.writeSourceCode = exports.convertDot2Svg = exports.writeOutputFiles = void 0; -const fs_1 = require("fs"); -const path_1 = __importDefault(require("path")); -const sync_1 = __importDefault(require("@aduh95/viz.js/sync")); -const { convert } = require('convert-svg-to-png'); -const debug = require('debug')('sol2uml'); +'use strict' +const __importDefault = + (this && this.__importDefault) || + function (mod) { + return mod && mod.__esModule ? mod : { default: mod } + } +Object.defineProperty(exports, '__esModule', { value: true }) +exports.writePng = + exports.writeSVG = + exports.writeDot = + exports.writeSourceCode = + exports.convertDot2Svg = + exports.writeOutputFiles = + void 0 +const fs_1 = require('fs') +const path_1 = __importDefault(require('path')) +const sync_1 = __importDefault(require('@aduh95/viz.js/sync')) +const { convert } = require('convert-svg-to-png') +const debug = require('debug')('sol2uml') /** * Writes output files to the file system based on the provided input and options. * @param dot The input string in DOT format. @@ -16,85 +24,93 @@ const debug = require('debug')('sol2uml'); * @param outputFormat The format of the output file. choices: svg, png, dot or all. default: png * @param outputFilename optional filename of the output file. */ -const writeOutputFiles = async (dot, contractName, outputFormat = 'svg', outputFilename) => { +const writeOutputFiles = async ( + dot, + contractName, + outputFormat = 'svg', + outputFilename, +) => { // If all output then extension is svg - const outputExt = outputFormat === 'all' ? 'svg' : outputFormat; + const outputExt = outputFormat === 'all' ? 'svg' : outputFormat if (!outputFilename) { outputFilename = - path_1.default.join(process.cwd(), contractName) + '.' + outputExt; - } - else { + path_1.default.join(process.cwd(), contractName) + '.' + outputExt + } else { // check if outputFilename is a folder try { - const folderOrFile = (0, fs_1.lstatSync)(outputFilename); + const folderOrFile = (0, fs_1.lstatSync)(outputFilename) if (folderOrFile.isDirectory()) { outputFilename = - path_1.default.join(process.cwd(), outputFilename, contractName) + - '.' + - outputExt; + path_1.default.join( + process.cwd(), + outputFilename, + contractName, + ) + + '.' + + outputExt } - } - catch (err) { } // we can ignore errors as it just means outputFilename does not exist yet + } catch (err) {} // we can ignore errors as it just means outputFilename does not exist yet } if (outputFormat === 'dot' || outputFormat === 'all') { - writeDot(dot, outputFilename); + writeDot(dot, outputFilename) // No need to continue if only generating a dot file if (outputFormat === 'dot') { - return; + return } } - const svg = convertDot2Svg(dot); + const svg = convertDot2Svg(dot) if (outputFormat === 'svg' || outputFormat === 'all') { - await writeSVG(svg, outputFilename, outputFormat); + await writeSVG(svg, outputFilename, outputFormat) } if (outputFormat === 'png' || outputFormat === 'all') { - await writePng(svg, outputFilename); + await writePng(svg, outputFilename) } -}; -exports.writeOutputFiles = writeOutputFiles; +} +exports.writeOutputFiles = writeOutputFiles function convertDot2Svg(dot) { - debug(`About to convert dot to SVG`); + debug('About to convert dot to SVG') try { - return (0, sync_1.default)(dot); - } - catch (err) { - console.error(`Failed to convert dot to SVG. ${err.message}`); - console.log(dot); - throw new Error(`Failed to parse dot string`, { cause: err }); + return (0, sync_1.default)(dot) + } catch (err) { + console.error(`Failed to convert dot to SVG. ${err.message}`) + console.log(dot) + throw new Error('Failed to parse dot string', { cause: err }) } } -exports.convertDot2Svg = convertDot2Svg; +exports.convertDot2Svg = convertDot2Svg function writeSourceCode(code, filename = 'source', extension = '.sol') { - const fileExtension = path_1.default.extname(filename); - const outputFile = fileExtension === extension ? filename : filename + extension; - debug(`About to write source code to file ${outputFile}`); - (0, fs_1.writeFile)(outputFile, code, (err) => { + const fileExtension = path_1.default.extname(filename) + const outputFile = + fileExtension === extension ? filename : filename + extension + debug(`About to write source code to file ${outputFile}`) + ;(0, fs_1.writeFile)(outputFile, code, (err) => { if (err) { - throw new Error(`Failed to write source code to file ${outputFile}`, { - cause: err, - }); - } - else { - console.log(`Source code written to ${outputFile}`); + throw new Error( + `Failed to write source code to file ${outputFile}`, + { + cause: err, + }, + ) + } else { + console.log(`Source code written to ${outputFile}`) } - }); + }) } -exports.writeSourceCode = writeSourceCode; +exports.writeSourceCode = writeSourceCode function writeDot(dot, filename) { - const dotFilename = changeFileExtension(filename, 'dot'); - debug(`About to write Dot file to ${dotFilename}`); - (0, fs_1.writeFile)(dotFilename, dot, (err) => { + const dotFilename = changeFileExtension(filename, 'dot') + debug(`About to write Dot file to ${dotFilename}`) + ;(0, fs_1.writeFile)(dotFilename, dot, (err) => { if (err) { throw new Error(`Failed to write Dot file to ${dotFilename}`, { cause: err, - }); - } - else { - console.log(`Dot file written to ${dotFilename}`); + }) + } else { + console.log(`Dot file written to ${dotFilename}`) } - }); + }) } -exports.writeDot = writeDot; +exports.writeDot = writeDot /** * Writes an SVG file to the file system. * @param svg The SVG input to be written to the file system. @@ -102,32 +118,36 @@ exports.writeDot = writeDot; * @param outputFormats The format of the output file. choices: svg, png, dot or all. default: png * @throws Error - If there is an error writing the SVG file. */ -function writeSVG(svg, svgFilename = 'classDiagram.svg', outputFormats = 'png') { - debug(`About to write SVG file to ${svgFilename}`); +function writeSVG( + svg, + svgFilename = 'classDiagram.svg', + outputFormats = 'png', +) { + debug(`About to write SVG file to ${svgFilename}`) if (outputFormats === 'png') { - const parsedFile = path_1.default.parse(svgFilename); + const parsedFile = path_1.default.parse(svgFilename) if (!parsedFile.dir) { - svgFilename = process.cwd() + '/' + parsedFile.name + '.svg'; - } - else { - svgFilename = parsedFile.dir + '/' + parsedFile.name + '.svg'; + svgFilename = process.cwd() + '/' + parsedFile.name + '.svg' + } else { + svgFilename = parsedFile.dir + '/' + parsedFile.name + '.svg' } } return new Promise((resolve, reject) => { - (0, fs_1.writeFile)(svgFilename, svg, (err) => { + ;(0, fs_1.writeFile)(svgFilename, svg, (err) => { if (err) { - reject(new Error(`Failed to write SVG file to ${svgFilename}`, { - cause: err, - })); - } - else { - console.log(`Generated svg file ${svgFilename}`); - resolve(); + reject( + new Error(`Failed to write SVG file to ${svgFilename}`, { + cause: err, + }), + ) + } else { + console.log(`Generated svg file ${svgFilename}`) + resolve() } - }); - }); + }) + }) } -exports.writeSVG = writeSVG; +exports.writeSVG = writeSVG /** * Asynchronously writes a PNG file to the file system from an SVG input. * @param svg - The SVG input to be converted to a PNG file. @@ -135,37 +155,41 @@ exports.writeSVG = writeSVG; * @throws Error - If there is an error converting or writing the PNG file. */ async function writePng(svg, filename) { - const pngFilename = changeFileExtension(filename, 'png'); - debug(`About to write png file ${pngFilename}`); + const pngFilename = changeFileExtension(filename, 'png') + debug(`About to write png file ${pngFilename}`) try { const png = await convert(svg, { outputFilePath: pngFilename, - }); + }) return new Promise((resolve, reject) => { - (0, fs_1.writeFile)(pngFilename, png, (err) => { + ;(0, fs_1.writeFile)(pngFilename, png, (err) => { if (err) { - reject(new Error(`Failed to write PNG file to ${pngFilename}`, { - cause: err, - })); - } - else { - console.log(`Generated png file ${pngFilename}`); - resolve(); + reject( + new Error( + `Failed to write PNG file to ${pngFilename}`, + { + cause: err, + }, + ), + ) + } else { + console.log(`Generated png file ${pngFilename}`) + resolve() } - }); - }); - } - catch (err) { + }) + }) + } catch (err) { throw new Error(`Failed to convert PNG file ${pngFilename}`, { cause: err, - }); + }) } } -exports.writePng = writePng; +exports.writePng = writePng // put a new file extension on a filename const changeFileExtension = (filename, extension) => { - const parsedFile = path_1.default.parse(filename); - const dir = parsedFile.dir === '' ? '.' : path_1.default.resolve(parsedFile.dir); - return dir + '/' + parsedFile.name + '.' + extension; -}; -//# sourceMappingURL=writerFiles.js.map \ No newline at end of file + const parsedFile = path_1.default.parse(filename) + const dir = + parsedFile.dir === '' ? '.' : path_1.default.resolve(parsedFile.dir) + return dir + '/' + parsedFile.name + '.' + extension +} +// # sourceMappingURL=writerFiles.js.map diff --git a/src/ts/__tests__/etherscanParser.test.ts b/src/ts/__tests__/etherscanParser.test.ts index ccc0b7c3..232b2b85 100644 --- a/src/ts/__tests__/etherscanParser.test.ts +++ b/src/ts/__tests__/etherscanParser.test.ts @@ -7,11 +7,11 @@ const etherDelta = '0x8d12A197cB00D4747a1fe03395095ce2A5CC6819' describe('Etherscan', () => { test('get source code', async () => { const etherscan = new EtherscanParser( - 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ' + 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ', ) const sourceCode = await etherscan.getSourceCode( - '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413' + '0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413', ) expect(sourceCode.files).toHaveLength(1) expect(sourceCode.contractName).toEqual('DAO') @@ -19,11 +19,11 @@ describe('Etherscan', () => { }) test('get source code files', async () => { const etherscan = new EtherscanParser( - 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ' + 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ', ) const sourceCode = await etherscan.getSourceCode( - '0xc1fc9E5eC3058921eA5025D703CBE31764756319' + '0xc1fc9E5eC3058921eA5025D703CBE31764756319', ) expect(sourceCode.files).toHaveLength(4) expect(sourceCode.contractName).toEqual('OETHMorphoAaveStrategyProxy') @@ -31,12 +31,12 @@ describe('Etherscan', () => { }) test('get source code file', async () => { const etherscan = new EtherscanParser( - 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ' + 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ', ) const sourceCode = await etherscan.getSourceCode( '0xc1fc9E5eC3058921eA5025D703CBE31764756319', - 'InitializeGovernedUpgradeabilityProxy' + 'InitializeGovernedUpgradeabilityProxy', ) expect(sourceCode.files).toHaveLength(1) expect(sourceCode.contractName).toEqual('OETHMorphoAaveStrategyProxy') @@ -44,7 +44,7 @@ describe('Etherscan', () => { }) test('Get UML Classes', async () => { const etherscan = new EtherscanParser( - 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ' + 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ', ) const { umlClasses } = await etherscan.getUmlClasses(etherDelta) @@ -56,16 +56,16 @@ describe('Etherscan', () => { expect.assertions(1) const etherscan = new EtherscanParser( - 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ' + 'HPD85TXCG1HW3N5G6JJXK1A7EE5K86CYBJ', ) try { await etherscan.getUmlClasses( - '0x0000000000000000000000000000000000000001' + '0x0000000000000000000000000000000000000001', ) } catch (err) { expect(err.message).toMatch( - /Failed to get verified source code for address 0x0000000000000000000000000000000000000001 from Etherscan API/ + /Failed to get verified source code for address 0x0000000000000000000000000000000000000001 from Etherscan API/, ) } }) diff --git a/src/ts/__tests__/storage.test.ts b/src/ts/__tests__/storage.test.ts index 52456757..6b254903 100644 --- a/src/ts/__tests__/storage.test.ts +++ b/src/ts/__tests__/storage.test.ts @@ -110,11 +110,11 @@ describe('storage parser', () => { const { size, dynamic } = calcStorageByteSize( attribute, umlClass, - [] + [], ) expect(size).toEqual(expectedSize) expect(dynamic).toEqual(expectedDynamic) - } + }, ) // TODO implement support for sizing expressions. eg @@ -207,7 +207,7 @@ describe('storage parser', () => { const { size, dynamic } = calcStorageByteSize( attribute, umlCLass, - otherClasses + otherClasses, ) expect(size).toEqual(expectedSize) expect(dynamic).toEqual(expectedDynamic) @@ -360,8 +360,8 @@ describe('storage parser', () => { type.slice(-1) === ']' ? AttributeType.Array : isElementary(type) - ? AttributeType.Elementary - : AttributeType.UserDefined + ? AttributeType.Elementary + : AttributeType.UserDefined testAttributes.push({ name: `test ${i}`, type, @@ -385,7 +385,7 @@ describe('storage parser', () => { const { size, dynamic } = calcStorageByteSize( attribute, umlCLass, - [...otherClasses, testStruct] + [...otherClasses, testStruct], ) expect(size).toEqual(expected) expect(dynamic).toEqual(false) @@ -396,13 +396,13 @@ describe('storage parser', () => { it('bytes to string', () => { expect( parseBytes32String( - '0x5465737453746f7261676520636f6e7472616374000000000000000000000000' - ) + '0x5465737453746f7261676520636f6e7472616374000000000000000000000000', + ), ).toEqual('TestStorage contract') }) it('string to bytes', () => { expect(formatBytes32String('Less than 31 bytes')).toEqual( - '0x4c657373207468616e2033312062797465730000000000000000000000000000' + '0x4c657373207468616e2033312062797465730000000000000000000000000000', ) }) }) @@ -426,7 +426,7 @@ describe('storage parser', () => { ${'0x01'} | ${'0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6'} `('slot $slot', ({ slot, expected }) => { expect(calcSectionOffset({ ...variable, fromSlot: slot })).toEqual( - expected + expected, ) }) })