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