@openzeppelin/upgrade-safe-transpiler
Version:
Solidity preprocessor used to generate OpenZeppelin Contracts Upgrade Safe.
100 lines • 4.78 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 ast_utils_1 = require("../solc/ast-utils");
const natspec_1 = require("../utils/natspec");
const type_id_1 = require("../utils/type-id");
const parse_type_id_1 = require("../utils/parse-type-id");
const is_storage_variable_1 = require("./utils/is-storage-variable");
// By default, make the contract a total of 50 slots (storage + gap)
const DEFAULT_SLOT_COUNT = 50;
function* addStorageGaps(sourceUnit, { getLayout, resolver }) {
for (const contract of (0, utils_1.findAll)('ContractDefinition', sourceUnit)) {
if (contract.contractKind === 'contract') {
const targetSlots = (0, natspec_1.extractContractStorageSize)(contract) ?? DEFAULT_SLOT_COUNT;
const gapSize = targetSlots - getContractSlotCount(contract, getLayout(contract), resolver);
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 getNumberOfBytesOfValueType(typeId, resolver) {
const { head, tail } = (0, parse_type_id_1.parseTypeId)(typeId);
const details = head.match(/^t_(?<base>[a-zA-Z]+)(?<size>\d+)?/);
switch (details?.groups?.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;
case 'userDefinedValueType': {
const definition = resolver.resolveNode('UserDefinedValueTypeDefinition', Number(tail));
const underlying = definition.underlyingType.typeDescriptions.typeIdentifier;
if (underlying) {
return getNumberOfBytesOfValueType(underlying, resolver);
}
else {
throw new Error(`Unsupported value type: ${typeId}`);
}
}
default:
throw new Error(`Unsupported value type: ${typeId}`);
}
}
function getContractSlotCount(contractNode, layout, resolver) {
// 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 ((0, is_storage_variable_1.isStorageVariable)(varDecl, resolver)) {
// try get type details
const typeIdentifier = (0, type_id_1.decodeTypeIdentifier)(varDecl.typeDescriptions.typeIdentifier ?? '');
// 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(layout.types[typeIdentifier]?.numberOfBytes ?? '')
: getNumberOfBytesOfValueType(typeIdentifier, resolver);
// 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