UNPKG

@openzeppelin/upgrades

Version:

JavaScript library for the OpenZeppelin smart contract platform

250 lines 10.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const lodash_1 = require("lodash"); const path_1 = __importDefault(require("path")); const process_1 = __importDefault(require("process")); const BuildArtifacts_1 = require("../artifacts/BuildArtifacts"); // TS-TODO: define return type after typing class members below. function getStorageLayout(contract, artifacts) { if (!artifacts) artifacts = BuildArtifacts_1.getBuildArtifacts(); const layout = new StorageLayout(contract, artifacts); const { types, storage } = layout.run(); return { types, storage }; } exports.getStorageLayout = getStorageLayout; // TS-TODO: Define parameter after typing class members below. // TS-TODO: define return type after typing class members below. function getStructsOrEnums(info) { return info.storage.filter((variable) => containsStructOrEnum(variable.type, info.types)); } exports.getStructsOrEnums = getStructsOrEnums; // TS-TODO: Define parameter types type after typing class members below. function containsStructOrEnum(typeName, types) { const type = types[typeName]; if (type.kind === 'struct' || type.kind === 'enum') return true; else if (type.valueType) return containsStructOrEnum(type.valueType, types); else return false; } const CONTRACT_TYPE_INFO = { id: 't_address', kind: 'elementary', label: 'address', }; const FUNCTION_TYPE_INFO = { id: 't_function', kind: 'elementary', label: 'function', }; class StorageLayout { constructor(contract, artifacts) { this.artifacts = artifacts; this.contract = contract; // Transitive closure of source files imported from the contract. this.imports = new Set(); // Map from ast id to nodeset across all visited contracts. // (Note that more than one node may have the same id, due to how truffle compiles artifacts). this.nodes = {}; // Types info being collected for the current contract. this.types = {}; // Storage layout for the current contract. this.storage = []; } run() { this.collectImports(this.contract.schema.ast); this.collectNodes(this.contract.schema.ast); // TS-TODO: define contractNode type. this.getLinearizedBaseContracts().forEach((contractNode) => { this.visitVariables(contractNode); }); return this; } // TS-TODO: could type ast from artifacts/web3. collectImports(ast) { ast.nodes .filter(node => node.nodeType === 'ImportDirective') .map(node => node.absolutePath) .forEach(importPath => { if (this.imports.has(importPath)) return; this.imports.add(importPath); this.artifacts.getArtifactsFromSourcePath(importPath).forEach(importedArtifact => { this.collectNodes(importedArtifact.ast); this.collectImports(importedArtifact.ast); }); }); } collectNodes(node) { // Return if we have already seen this node. if (lodash_1.some(this.nodes[node.id] || [], n => lodash_1.isEqual(n, node))) return; // Add node to collection with this id otherwise. if (!this.nodes[node.id]) this.nodes[node.id] = []; this.nodes[node.id].push(node); // Call recursively to children. if (node.nodes) node.nodes.forEach(this.collectNodes.bind(this)); } visitVariables(contractNode) { const sourcePath = path_1.default.relative(process_1.default.cwd(), this.getNode(contractNode.scope, 'SourceUnit').absolutePath); const varNodes = contractNode.nodes.filter((node) => node.stateVariable && !node.constant); varNodes.forEach(node => { const typeInfo = this.getAndRegisterTypeInfo(node.typeName); this.registerType(typeInfo); const storageInfo = Object.assign({ contract: contractNode.name, path: sourcePath }, this.getStorageInfo(node, typeInfo)); this.storage.push(storageInfo); }); } registerType(typeInfo) { this.types[typeInfo.id] = typeInfo; } getNode(id, nodeType) { if (!this.nodes[id]) throw Error(`No AST nodes with id ${id} found`); const candidates = this.nodes[id].filter(node => node.nodeType === nodeType); switch (candidates.length) { case 0: throw Error(`No AST nodes of type ${nodeType} with id ${id} found (got ${this.nodes[id] .map((node) => node.nodeType) .join(', ')})`); case 1: return candidates[0]; default: throw Error(`Found more than one node of type ${nodeType} with the same id ${id}. Please try clearing your build artifacts and recompiling your contracts.`); } } getContractNode() { return this.contract.schema.ast.nodes.find(node => node.nodeType === 'ContractDefinition' && node.name === this.contract.schema.contractName); } getLinearizedBaseContracts() { return lodash_1.reverse(this.getContractNode().linearizedBaseContracts.map((id) => this.getNode(id, 'ContractDefinition'))); } getStorageInfo(varNode, typeInfo) { return { label: varNode.name, astId: varNode.id, type: typeInfo.id, src: varNode.src, }; } getAndRegisterTypeInfo(node) { const typeInfo = this.getTypeInfo(node); this.registerType(typeInfo); return typeInfo; } getTypeInfo(node) { switch (node.nodeType) { case 'ElementaryTypeName': return this.getElementaryTypeInfo(node); case 'ArrayTypeName': return this.getArrayTypeInfo(node); case 'Mapping': return this.getMappingTypeInfo(node); case 'UserDefinedTypeName': return this.getUserDefinedTypeInfo(node); case 'FunctionTypeName': return this.getFunctionTypeInfo(); default: throw Error(`Cannot get type info for unknown node type ${node.nodeType}`); } } getUserDefinedTypeInfo({ referencedDeclaration, typeDescriptions }) { const typeIdentifier = this.getTypeIdentifier(typeDescriptions); switch (typeIdentifier) { case 't_contract': return this.getContractTypeInfo(); case 't_struct': return this.getStructTypeInfo(referencedDeclaration); case 't_enum': return this.getEnumTypeInfo(referencedDeclaration); default: throw Error(`Unknown type identifier ${typeIdentifier} for ${typeDescriptions.typeString}`); } } getTypeIdentifier({ typeIdentifier }) { return typeIdentifier.split('$', 1)[0]; } getElementaryTypeInfo({ typeDescriptions }) { const identifier = typeDescriptions.typeIdentifier.replace(/_storage(_ptr)?$/, ''); return { id: identifier, kind: 'elementary', label: typeDescriptions.typeString, }; } getArrayTypeInfo({ baseType, length }) { const { id: baseTypeId, label: baseTypeLabel } = this.getAndRegisterTypeInfo(baseType); const lengthDescriptor = length ? length.value : 'dyn'; const lengthLabel = length ? length.value : ''; return { id: `t_array:${lengthDescriptor}<${baseTypeId}>`, valueType: baseTypeId, length: lengthDescriptor, kind: 'array', label: `${baseTypeLabel}[${lengthLabel}]`, }; } getMappingTypeInfo({ valueType }) { // We ignore the keyTypeId, since it's always hashed and takes up the same amount of space; we only care about the last value type const { id: valueTypeId, label: valueTypeLabel } = this.getValueTypeInfo(valueType); return { id: `t_mapping<${valueTypeId}>`, valueType: valueTypeId, label: `mapping(key => ${valueTypeLabel})`, kind: 'mapping', }; } getContractTypeInfo() { // Process a reference to a contract as an address, since we only care about storage size return Object.assign({}, CONTRACT_TYPE_INFO); } getFunctionTypeInfo() { // Process a reference to a function disregarding types, since we only care how much space it takes return Object.assign({}, FUNCTION_TYPE_INFO); } getStructTypeInfo(referencedDeclaration) { // Identify structs by contract and name const referencedNode = this.getNode(referencedDeclaration, 'StructDefinition'); const id = `t_struct<${referencedNode.canonicalName}>`; if (this.types[id]) return this.types[id]; // We shortcircuit type registration in this scenario to handle recursive structs const typeInfo = { id, kind: 'struct', label: referencedNode.canonicalName, members: [], }; this.registerType(typeInfo); // Store members info in type info const members = referencedNode.members .filter(member => member.nodeType === 'VariableDeclaration') .map(member => { const memberTypeInfo = this.getAndRegisterTypeInfo(member.typeName); return this.getStorageInfo(member, memberTypeInfo); }); Object.assign(typeInfo, { members }); return typeInfo; } getEnumTypeInfo(referencedDeclaration) { // Store canonical name and members for an enum const referencedNode = this.getNode(referencedDeclaration, 'EnumDefinition'); return { id: `t_enum<${referencedNode.canonicalName}>`, kind: 'enum', label: referencedNode.canonicalName, members: referencedNode.members.map(m => m.name), }; } getValueTypeInfo(node) { return node.nodeType === 'Mapping' ? this.getValueTypeInfo(node.valueType) : this.getAndRegisterTypeInfo(node); } } //# sourceMappingURL=Storage.js.map