UNPKG

@solvprotocol/upgrade-safe-transpiler

Version:

Solidity preprocessor used to generate OpenZeppelin Contracts Upgrade Safe.

178 lines 9.59 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.buildSuperCallsForChain = void 0; const lodash_1 = require("lodash"); const ast_utils_1 = require("../../solc/ast-utils"); const upgrades_overrides_1 = require("../../utils/upgrades-overrides"); const get_initializer_items_1 = require("./get-initializer-items"); const utils_1 = require("solidity-ast/utils"); // builds an __init call with given arguments, for example // ERC20DetailedUpgradeable.__init(false, "Gold", "GLD", 18) function buildSuperCall(args, name, helper) { let superCall = `__${name}_init_unchained(`; if (args && args.length) { superCall += args.map(arg => helper.read(arg)).join(', '); } return superCall + ');'; } // builds all the __init calls a given contract, for example // ContextUpgradeable.__init(false); // ERC20DetailedUpgradeable.__init(false, 'Gold', 'GLD', 18); function buildSuperCallsForChain(contractNode, { resolver }, helper) { var _a, _b, _c; // first we get the linearized inheritance chain of contracts, excluding the // contract we're currently looking at const chain = contractNode.linearizedBaseContracts.map(baseId => { const base = resolver.resolveContract(baseId); if (base === undefined) { throw new Error(`Could not resolve ast id ${baseId}`); } return base; }); // we will need their ast ids for quick lookup const chainIds = new Set(chain.map(c => c.id)); // now we gather all constructor calls taken from the two possible sources // 1) "modifiers" on parent constructors, and // 2) arguments in the inheritance list (contract X is Y, Z(arg1, arg2) ...) // since the contract was compiled successfully, we are guaranteed that each base contract // will show up in at most one of these two places across all contracts in the chain (can also be zero) const ctorCalls = (0, lodash_1.keyBy)((0, lodash_1.flatten)(chain.map(parentNode => { const res = []; const constructorNode = (0, ast_utils_1.getConstructor)(parentNode); if (constructorNode) { for (const call of constructorNode.modifiers) { // we only care about modifiers that reference base contracts const { referencedDeclaration } = call.modifierName; if (referencedDeclaration != null && chainIds.has(referencedDeclaration) && call.arguments != null && call.arguments.length > 0) { res.push({ call }); } } } for (const call of parentNode.baseContracts) { if (call.arguments != null && call.arguments.length > 0) { res.push({ call }); } } return res; })), mod => { if (mod.call.nodeType === 'ModifierInvocation') { if (mod.call.modifierName.referencedDeclaration == null) { throw new Error('Missing referencedDeclaration field'); } return mod.call.modifierName.referencedDeclaration; } else { return mod.call.baseName.referencedDeclaration; } }); const invalidReference = new Set(); const notInitializable = new Set(); const markNotInitializable = (parentNode) => { var _a, _b, _c; const parameters = (_c = (_b = (_a = (0, ast_utils_1.getConstructor)(parentNode)) === null || _a === void 0 ? void 0 : _a.parameters) === null || _b === void 0 ? void 0 : _b.parameters) !== null && _c !== void 0 ? _c : []; for (const { id } of parameters) { invalidReference.add(id); } notInitializable.add(parentNode.id); }; const argsValues = new Map(); const parentArgsValues = new Map(); for (const parentNode of chain) { if (parentNode === contractNode) { continue; } const ctorCallArgs = (_b = (_a = ctorCalls[parentNode.id]) === null || _a === void 0 ? void 0 : _a.call) === null || _b === void 0 ? void 0 : _b.arguments; if (!ctorCallArgs) { // We don't have arguments for this parent, but it may be implicitly constructed (has zero args) if (isImplicitlyConstructed(parentNode)) { parentArgsValues.set(parentNode, []); } else { // If a parent is not initializable, we assume its parents might be initializable either, // because we may not have their constructor arguments. So we save the arguments in case // other parent reference it. markNotInitializable(parentNode); } } else { // We have arguments for this parent constructor, but they may include references to the constructor parameters of // "intermediate parents". We check all of these arguments for such references, and make sure they work with the // variables in scope. const parameters = (0, ast_utils_1.getConstructor)(parentNode).parameters.parameters; const parentArgs = ctorCallArgs.map((arg, index) => { const param = parameters[index]; if (arg.nodeType === 'Identifier') { // We have something like `constructor(uint x) Parent(x)`. // We have to get the value associated to this "source param" `uint x`, if any. const sourceParam = resolver.resolveNode('VariableDeclaration', arg.referencedDeclaration); const sourceValue = argsValues.get(sourceParam); if (invalidReference.has(arg.referencedDeclaration)) { // This parentNode is the parent of an uninitializable contract and uses a parameter that won't be in the context. markNotInitializable(parentNode); } else if (sourceValue) { if (sourceValue.nodeType === 'Literal' || sourceValue.nodeType === 'Identifier') { // If the source value is a literal or another identifier, we use it as the argument. arg = argsValues.get(sourceParam); } else { // If the source value is some other expression, this would be the second time it's used and we // reject this as it may have side effects. throw new Error(`Can't transpile non-trivial expression in parent constructor argument (${helper.read(sourceValue)})`); } } } else { // We have something like `constructor(...) Parent(<expr>)` where the expression is not a simple identifier. // We will only allow this expression if it is correct in the new context without any changes. const identifiers = [...(0, utils_1.findAll)('Identifier', arg)]; for (const id of identifiers) { const sourceParam = resolver.tryResolveNode('VariableDeclaration', id.referencedDeclaration); const sourceValue = sourceParam && argsValues.get(sourceParam); if (invalidReference.has(id.referencedDeclaration)) { // This parentNode is the parent of an uninitializable contract and uses a parameter that won't be in the context. markNotInitializable(parentNode); } else if (sourceValue && (sourceValue.nodeType !== 'Identifier' || sourceValue.name !== id.name)) { // The variable gets its value from a child constructor, and it's not another variable with the same name. throw new Error(`Can't transpile non-trivial expression in parent constructor argument (${helper.read(arg)})`); } } } argsValues.set(param, arg); return arg; }); parentArgsValues.set(parentNode, parentArgs); } } // once we have gathered all constructor calls for each parent, we linearize // them according to chain. const linearizedCtorCalls = []; chain.reverse(); for (const parentNode of chain) { if (parentNode === contractNode || (0, upgrades_overrides_1.hasConstructorOverride)(parentNode) || parentNode.contractKind === 'interface' || notInitializable.has(parentNode.id)) { continue; } const args = (_c = parentArgsValues.get(parentNode)) !== null && _c !== void 0 ? _c : []; if (args.length || !(0, get_initializer_items_1.getInitializerItems)(parentNode).emptyUnchained) { // TODO: we have to use the name in the lexical context and not necessarily // the original contract name linearizedCtorCalls.push(buildSuperCall(args, parentNode.name, helper)); } } return linearizedCtorCalls; } exports.buildSuperCallsForChain = buildSuperCallsForChain; function isImplicitlyConstructed(contract) { const ctor = (0, ast_utils_1.getConstructor)(contract); return (contract.contractKind === 'contract' && (ctor == undefined || ctor.parameters.parameters.length === 0)); } //# sourceMappingURL=build-super-calls-for-chain.js.map