@openzeppelin/upgrade-safe-transpiler
Version:
Solidity preprocessor used to generate OpenZeppelin Contracts Upgrade Safe.
165 lines • 8.7 kB
JavaScript
;
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