UNPKG

@openzeppelin/upgrade-safe-transpiler

Version:

Solidity preprocessor used to generate OpenZeppelin Contracts Upgrade Safe.

165 lines 8.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getNamespaceStructName = getNamespaceStructName; exports.addNamespaceStruct = addNamespaceStruct; const utils_1 = require("solidity-ast/utils"); const ast_utils_1 = require("../solc/ast-utils"); const format_lines_1 = require("./utils/format-lines"); const is_storage_variable_1 = require("./utils/is-storage-variable"); const erc7201_1 = require("../utils/erc7201"); const contract_start_position_1 = require("./utils/contract-start-position"); const natspec_1 = require("../utils/natspec"); function getNamespaceStructName(contractName) { return contractName + 'Storage'; } function addNamespaceStruct(include) { return function* (sourceUnit, tools) { if (!include?.(sourceUnit.absolutePath)) { return; } const { error, resolver } = tools; for (const contract of (0, utils_1.findAll)('ContractDefinition', sourceUnit)) { const specifiesStorageSize = (0, natspec_1.extractContractStorageSize)(contract) !== undefined; if (specifiesStorageSize) { throw tools.error(contract, 'Cannot combine namespaces with @custom:storage-size annotations'); } let start = (0, contract_start_position_1.contractStartPosition)(contract, tools); let finished = false; const nonStorageVars = []; const storageVars = []; // We look for the start of the source code block in the contract // where variables are written for (const n of contract.nodes) { if (n.nodeType === 'VariableDeclaration' && (storageVars.length > 0 || (0, is_storage_variable_1.isStorageVariable)(n, resolver))) { if (finished) { throw error(n, 'All variables in the contract must be contiguous'); } if (!(0, is_storage_variable_1.isStorageVariable)(n, resolver)) { const varStart = getRealEndIndex(storageVars.at(-1), tools) + 1; nonStorageVars.push([varStart, n]); } else { storageVars.push(n); } } else if (storageVars.length > 0) { // We've seen storage variables before and the current node is not a // variable, so we consider the block to have finished finished = true; } else { // We haven't found storage variables yet. We assume the block of // variables will start after the current node start = getRealEndIndex(n, tools) + 1; } } if (storageVars.length > 0) { // We first move non-storage variables from their location to the beginning of // the block, so they are excluded from the namespace struct for (const [s, v] of nonStorageVars) { const bounds = { start: s, length: getRealEndIndex(v, tools) + 1 - s }; let removed = ''; yield { kind: 'relocate-nonstorage-var-remove', ...bounds, transform: source => { removed = source; return ''; }, }; yield { kind: 'relocate-nonstorage-var-reinsert', start, length: 0, text: removed, }; } if (nonStorageVars.length > 0) { yield { kind: 'relocate-nonstorage-var-newline', start, length: 0, text: '\n', }; } for (const v of storageVars) { const { start, length } = (0, ast_utils_1.getNodeBounds)(v); yield { kind: 'remove-var-modifier', start, length, transform: source => source.replace(/\s*\bprivate\b/g, ''), }; } const namespace = getNamespaceStructName(contract.name); const id = 'openzeppelin.storage.' + contract.name; const end = getRealEndIndex(storageVars.at(-1), tools) + 1; yield { kind: 'add-namespace-struct', start, length: end - start, transform: source => { // We extract the newlines at the beginning of the block so we can leave // them outside of the struct definition const [, leadingNewlines, rest] = source.match(/^((?:[ \t\v\f]*[\n\r])*)(.*)$/s); return (leadingNewlines + (0, format_lines_1.formatLines)(1, [ `/// @custom:storage-location erc7201:${id}`, `struct ${namespace} {`, ...rest.split('\n'), `}`, ``, `// keccak256(abi.encode(uint256(keccak256("${id}")) - 1)) & ~bytes32(uint256(0xff))`, `bytes32 private constant ${namespace}Location = ${(0, erc7201_1.erc7201Location)(id)};`, ``, `function _get${namespace}() private pure returns (${namespace} storage $) {`, [`assembly {`, [`$.slot := ${namespace}Location`], `}`], `}`, ]).trimEnd()); }, }; for (const fnDef of (0, utils_1.findAll)('FunctionDefinition', contract)) { for (const ref of fnDef.modifiers.flatMap(m => [...(0, utils_1.findAll)('Identifier', m)])) { const varDecl = resolver.tryResolveNode('VariableDeclaration', ref.referencedDeclaration); if (varDecl && (0, is_storage_variable_1.isStorageVariable)(varDecl, resolver)) { throw error(ref, 'Unsupported storage variable found in modifier'); } } let foundReferences = false; if (fnDef.body) { for (const ref of (0, utils_1.findAll)('Identifier', fnDef.body)) { const varDecl = resolver.tryResolveNode('VariableDeclaration', ref.referencedDeclaration); if (varDecl && (0, is_storage_variable_1.isStorageVariable)(varDecl, resolver)) { if (varDecl.scope !== contract.id) { throw error(varDecl, 'Namespaces assume all variables are private'); } foundReferences = true; const { start } = (0, ast_utils_1.getNodeBounds)(ref); yield { kind: 'add-namespace-ref', start, length: 0, text: '$.' }; } } if (fnDef.kind !== 'constructor' && foundReferences) { // The constructor is handled in transformConstructor const { start: fnBodyStart } = (0, ast_utils_1.getNodeBounds)(fnDef.body); yield { kind: 'add-namespace-base-ref', start: fnBodyStart + 1, length: 0, text: `\n ${namespace} storage $ = _get${namespace}();`, }; } } } } } }; } function getRealEndIndex(node, tools) { // VariableDeclaration node bounds don't include the semicolon, so we look for it, // and include a comment if there is one after the node. // This regex always matches at least the empty string. const { start, length } = tools.matchOriginalAfter(node, /(\s*;)?([ \t]*\/\/[^\n\r]*)?/); return start + length - 1; } //# sourceMappingURL=add-namespace-struct.js.map