UNPKG

@solvprotocol/upgrade-safe-transpiler

Version:

Solidity preprocessor used to generate OpenZeppelin Contracts Upgrade Safe.

106 lines 4.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.addStorageGaps = void 0; const utils_1 = require("solidity-ast/utils"); const format_lines_1 = require("./utils/format-lines"); const upgrades_overrides_1 = require("../utils/upgrades-overrides"); const ast_utils_1 = require("../solc/ast-utils"); const extractNatspec_1 = require("../utils/extractNatspec"); const type_id_1 = require("../utils/type-id"); const parse_type_id_1 = require("../utils/parse-type-id"); // By default, make the contract a total of 50 slots (storage + gap) const DEFAULT_SLOT_COUNT = 60; function* addStorageGaps(sourceUnit, { getLayout }) { for (const contract of (0, utils_1.findAll)('ContractDefinition', sourceUnit)) { if (contract.contractKind === 'contract') { let targetSlots = DEFAULT_SLOT_COUNT; for (const entry of (0, extractNatspec_1.extractNatspec)(contract)) { if (entry.title === 'custom' && entry.tag === 'storage-size') { targetSlots = parseInt(entry.args); } } const gapSize = targetSlots - getContractSlotCount(contract, getLayout(contract)); if (gapSize <= 0) { throw new Error(`Contract ${contract.name} uses more than the ${targetSlots} reserved slots.`); } const contractBounds = (0, ast_utils_1.getNodeBounds)(contract); const start = contractBounds.start + contractBounds.length - 1; const text = (0, format_lines_1.formatLines)(0, [ ``, [ `/**`, ` * @dev This empty reserved space is put in place to allow future versions to add new`, ` * variables without shifting down storage in the inheritance chain.`, ` * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps`, ` */`, `uint256[${gapSize}] private __gap;`, ], ]); yield { kind: 'add-storage-gaps', start, length: 0, text, }; } } } exports.addStorageGaps = addStorageGaps; function isStorageVariable(varDecl) { switch (varDecl.mutability) { case 'constant': return false; case 'immutable': return !(0, upgrades_overrides_1.hasOverride)(varDecl, 'state-variable-immutable'); default: return true; } } function getNumberOfBytesOfValueType(typeId) { var _a; const details = (0, parse_type_id_1.parseTypeId)(typeId).head.match(/^t_(?<base>[a-z]+)(?<size>\d+)?/); switch ((_a = details === null || details === void 0 ? void 0 : details.groups) === null || _a === void 0 ? void 0 : _a.base) { case 'bool': case 'byte': case 'enum': return 1; case 'address': case 'contract': return 20; case 'bytes': return parseInt(details.groups.size, 10); case 'int': case 'uint': return parseInt(details.groups.size, 10) / 8; default: throw new Error(`Unsupported value type: ${typeId}`); } } function getContractSlotCount(contractNode, layout) { var _a, _b, _c; // This tracks both slot and offset: // - slot = Math.floor(contractSizeInBytes / 32) // - offset = contractSizeInBytes % 32 let contractSizeInBytes = 0; // don't use `findAll` here, we don't want to go recursive for (const varDecl of contractNode.nodes.filter((0, utils_1.isNodeType)('VariableDeclaration'))) { if (isStorageVariable(varDecl)) { // try get type details const typeIdentifier = (0, type_id_1.decodeTypeIdentifier)((_a = varDecl.typeDescriptions.typeIdentifier) !== null && _a !== void 0 ? _a : ''); // size of current object from type details, or try to reconstruct it if // they're not available try to reconstruct it, which can happen for // immutable variables const size = layout.types && layout.types[typeIdentifier] ? parseInt((_c = (_b = layout.types[typeIdentifier]) === null || _b === void 0 ? void 0 : _b.numberOfBytes) !== null && _c !== void 0 ? _c : '') : getNumberOfBytesOfValueType(typeIdentifier); // used space in the current slot const offset = contractSizeInBytes % 32; // remaining space in the current slot (only if slot is dirty) const remaining = (32 - offset) % 32; // if the remaining space is not enough to fit the current object, then consume the free space to start at next slot contractSizeInBytes += (size > remaining ? remaining : 0) + size; } } return Math.ceil(contractSizeInBytes / 32); } //# sourceMappingURL=add-storage-gaps.js.map