@neo-one/smart-contract-compiler
Version:
NEO•ONE TypeScript smart contract compiler.
404 lines (402 loc) • 22.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ContractInfoProcessor = void 0;
const tslib_1 = require("tslib");
const ts_utils_1 = require("@neo-one/ts-utils");
const utils_1 = require("@neo-one/utils");
const lodash_1 = tslib_1.__importDefault(require("lodash"));
const typescript_1 = tslib_1.__importDefault(require("typescript"));
const constants_1 = require("../compile/constants");
const types_1 = require("../compile/helper/types");
const constants_2 = require("../constants");
const DiagnosticCode_1 = require("../DiagnosticCode");
const DiagnosticMessage_1 = require("../DiagnosticMessage");
const utils_2 = require("../utils");
class ContractInfoProcessor {
constructor(context, smartContract) {
this.context = context;
this.smartContract = smartContract;
}
process() {
if (ts_utils_1.tsUtils.modifier.isAbstract(this.smartContract)) {
this.context.reportError(this.smartContract, DiagnosticCode_1.DiagnosticCode.InvalidContract, DiagnosticMessage_1.DiagnosticMessage.InvalidContractAbstract);
}
const result = this.processClass(this.smartContract, this.context.analysis.getType(this.smartContract));
const hasReceive = result.propInfos.some((propInfo) => propInfo.type === 'function' && propInfo.receive);
const refundAssets = hasReceive
? {
type: 'refundAssets',
name: constants_2.ContractPropertyName.refundAssets,
classDecl: this.smartContract,
isPublic: true,
}
: undefined;
const hasSend = result.propInfos.some((propInfo) => propInfo.type === 'function' && propInfo.send);
const completeSend = hasSend
? {
type: 'completeSend',
name: constants_2.ContractPropertyName.completeSend,
classDecl: this.smartContract,
isPublic: true,
}
: undefined;
const approveUpgrade = this.getApproveUpgradeDecl(result);
const upgrade = approveUpgrade !== undefined
? {
type: 'upgrade',
name: constants_2.ContractPropertyName.upgrade,
classDecl: this.smartContract,
isPublic: true,
approveUpgrade,
}
: undefined;
const finalPropInfos = result.propInfos.concat([refundAssets, completeSend, upgrade].filter(utils_1.utils.notNull));
if (this.hasDeployInfo(result)) {
return Object.assign(Object.assign({}, result), { propInfos: finalPropInfos });
}
return Object.assign(Object.assign({}, result), { propInfos: finalPropInfos.concat([
{
type: 'deploy',
name: constants_2.ContractPropertyName.deploy,
classDecl: this.smartContract,
isPublic: true,
isMixinDeploy: false,
},
]) });
}
processClass(classDecl, classType, baseTypes = []) {
if (classType === undefined) {
return { smartContract: classDecl, propInfos: [] };
}
ts_utils_1.tsUtils.class_
.getStaticMembers(classDecl)
.map((member) => ts_utils_1.tsUtils.modifier.getStaticKeyword(member))
.filter(utils_1.utils.notNull)
.forEach((keyword) => {
this.context.reportError(keyword, DiagnosticCode_1.DiagnosticCode.InvalidContractMethod, DiagnosticMessage_1.DiagnosticMessage.InvalidContractPropertyOrMethodStatic);
});
lodash_1.default.flatten(ts_utils_1.tsUtils.class_.getMembers(classDecl).map((member) => ts_utils_1.tsUtils.decoratable.getDecoratorsArray(member)))
.filter((decorator) => !this.isValidDecorator(decorator))
.forEach((decorator) => {
this.context.reportError(decorator, DiagnosticCode_1.DiagnosticCode.UnsupportedSyntax, DiagnosticMessage_1.DiagnosticMessage.UnsupportedDecorator);
});
lodash_1.default.flatten(ts_utils_1.tsUtils.class_
.getMethods(classDecl)
.map((method) => lodash_1.default.flatten(ts_utils_1.tsUtils.parametered.getParameters(method).map((param) => ts_utils_1.tsUtils.decoratable.getDecoratorsArray(param)))))
.filter((decorator) => !this.isValidDecorator(decorator))
.forEach((decorator) => {
this.context.reportError(decorator, DiagnosticCode_1.DiagnosticCode.UnsupportedSyntax, DiagnosticMessage_1.DiagnosticMessage.UnsupportedDecorator);
});
lodash_1.default.flatten(ts_utils_1.tsUtils.class_
.getSetAccessors(classDecl)
.map((method) => lodash_1.default.flatten(ts_utils_1.tsUtils.parametered.getParameters(method).map((param) => ts_utils_1.tsUtils.decoratable.getDecoratorsArray(param)))))
.filter((decorator) => !this.isValidDecorator(decorator))
.forEach((decorator) => {
this.context.reportError(decorator, DiagnosticCode_1.DiagnosticCode.UnsupportedSyntax, DiagnosticMessage_1.DiagnosticMessage.UnsupportedDecorator);
});
let propInfos = ts_utils_1.tsUtils.type_
.getProperties(classType)
.map((symbol) => this.processProperty(symbol))
.filter(utils_1.utils.notNull);
const ctor = ts_utils_1.tsUtils.class_.getConcreteConstructor(classDecl);
const ctorType = ctor === undefined
? undefined
: this.context.analysis.getTypeOfSymbol(this.context.analysis.getSymbol(ctor.parent), ctor.parent);
if (ctor !== undefined && ctorType !== undefined) {
const callSignatures = ctorType.getConstructSignatures();
if (callSignatures.length !== 1) {
this.context.reportError(ctor, DiagnosticCode_1.DiagnosticCode.InvalidContractMethod, DiagnosticMessage_1.DiagnosticMessage.InvalidContractMethodMultipleSignatures);
}
const callSignature = callSignatures[0];
const maybeFunc = ts_utils_1.tsUtils.node.getFirstAncestorByTest(ctor, (value) => typescript_1.default.isFunctionDeclaration(value) || typescript_1.default.isFunctionExpression(value));
propInfos = propInfos.concat([
{
type: 'deploy',
name: constants_2.ContractPropertyName.deploy,
classDecl,
decl: ctor,
isPublic: true,
callSignature,
isMixinDeploy: maybeFunc !== undefined && this.context.analysis.isSmartContractMixinFunction(maybeFunc),
},
]);
}
const extend = ts_utils_1.tsUtils.class_.getExtends(classDecl);
let superSmartContract;
if (extend !== undefined) {
let baseType = baseTypes[0];
let nextBaseTypes = baseTypes.slice(1);
if (baseTypes.length === 0) {
const currentBaseTypes = ts_utils_1.tsUtils.class_.getBaseTypesFlattened(this.context.typeChecker, classDecl);
baseType = currentBaseTypes[0];
nextBaseTypes = currentBaseTypes.slice(1);
}
if (baseType !== undefined) {
const baseSymbol = this.context.analysis.getSymbolForType(classDecl, baseType);
if (baseSymbol !== undefined) {
const decls = ts_utils_1.tsUtils.symbol.getDeclarations(baseSymbol);
const decl = decls[0];
if (decls.length === 1 &&
(typescript_1.default.isClassDeclaration(decl) || typescript_1.default.isClassExpression(decl)) &&
!this.context.builtins.isValue(decl, 'SmartContract')) {
superSmartContract = this.processClass(decl, baseType, nextBaseTypes);
}
}
}
}
const contractInfo = { smartContract: classDecl, propInfos, superSmartContract };
if (contractInfo.propInfos.every((propInfo) => propInfo.type !== 'deploy') &&
contractInfo.propInfos.some((propInfo) => propInfo.type === 'property' && ts_utils_1.tsUtils.initializer.getInitializer(propInfo.decl) !== undefined)) {
return Object.assign(Object.assign({}, contractInfo), { propInfos: contractInfo.propInfos.concat([
{
type: 'deploy',
name: constants_2.ContractPropertyName.deploy,
classDecl: this.smartContract,
isPublic: true,
isMixinDeploy: false,
},
]) });
}
return contractInfo;
}
processProperty(symbol) {
const decls = ts_utils_1.tsUtils.symbol.getDeclarations(symbol);
const implDecls = decls.filter((symbolDecl) => (!(typescript_1.default.isMethodDeclaration(symbolDecl) || typescript_1.default.isConstructorDeclaration(symbolDecl)) ||
ts_utils_1.tsUtils.overload.isImplementation(symbolDecl)) &&
(!typescript_1.default.isPropertyDeclaration(symbolDecl) || !ts_utils_1.tsUtils.modifier.isAbstract(symbolDecl)));
const decl = implDecls.length > 0 ? implDecls[0] : decls[0];
if (decl === undefined ||
!(typescript_1.default.isMethodDeclaration(decl) ||
typescript_1.default.isPropertyDeclaration(decl) ||
typescript_1.default.isGetAccessorDeclaration(decl) ||
typescript_1.default.isSetAccessorDeclaration(decl) ||
typescript_1.default.isParameterPropertyDeclaration(decl, decl.parent))) {
return undefined;
}
const nameNode = ts_utils_1.tsUtils.node.getNameNode(decl);
if (!typescript_1.default.isIdentifier(nameNode)) {
this.context.reportError(nameNode, DiagnosticCode_1.DiagnosticCode.InvalidContractProperty, DiagnosticMessage_1.DiagnosticMessage.InvalidContractPropertyIdentifier);
return undefined;
}
const name = ts_utils_1.tsUtils.symbol.getName(symbol);
if (constants_2.IGNORED_PROPERTIES.has(name)) {
return undefined;
}
if (constants_2.BUILTIN_PROPERTIES.has(name)) {
const memberSymbol = this.context.builtins.getOnlyMemberSymbol('SmartContract', name);
if (symbol !== memberSymbol) {
this.context.reportError(nameNode, DiagnosticCode_1.DiagnosticCode.InvalidContractProperty, DiagnosticMessage_1.DiagnosticMessage.InvalidContractPropertyReserved, name);
}
return undefined;
}
if (constants_2.RESERVED_PROPERTIES.has(name)) {
const valueSymbol = this.context.builtins.getValueSymbol('SmartContract');
const memberSymbol = ts_utils_1.tsUtils.symbol.getMemberOrThrow(valueSymbol, name);
if (ts_utils_1.tsUtils.symbol.getTarget(symbol) !== memberSymbol) {
this.context.reportError(nameNode, DiagnosticCode_1.DiagnosticCode.InvalidContractProperty, DiagnosticMessage_1.DiagnosticMessage.InvalidContractPropertyReserved, name);
}
return undefined;
}
if (constants_2.VIRTUAL_PROPERTIES.has(name)) {
this.context.reportError(nameNode, DiagnosticCode_1.DiagnosticCode.InvalidContractMethod, DiagnosticMessage_1.DiagnosticMessage.InvalidContractMethodReserved, name);
return undefined;
}
const type = this.context.analysis.getTypeOfSymbol(symbol, decl);
if (type === undefined) {
return undefined;
}
const maybeClassDecl = ts_utils_1.tsUtils.node.getFirstAncestorByTest(decl, typescript_1.default.isClassDeclaration);
const maybeClassExpr = ts_utils_1.tsUtils.node.getFirstAncestorByTest(decl, typescript_1.default.isClassExpression);
const classDecl = maybeClassDecl === undefined ? maybeClassExpr : maybeClassDecl;
if (classDecl === undefined) {
this.context.reportUnsupported(decl);
return undefined;
}
const isPublic = ts_utils_1.tsUtils.modifier.isPublic(decl);
if (typescript_1.default.isGetAccessorDeclaration(decl) || typescript_1.default.isSetAccessorDeclaration(decl)) {
const getDecl = typescript_1.default.isGetAccessorDeclaration(decl) ? decl : ts_utils_1.tsUtils.accessor.getGetAccessor(decl);
const setDecl = typescript_1.default.isSetAccessorDeclaration(decl) ? decl : ts_utils_1.tsUtils.accessor.getSetAccessor(decl);
return {
type: 'accessor',
symbol: ts_utils_1.tsUtils.symbol.getTarget(symbol),
name: ts_utils_1.tsUtils.node.getName(decl),
classDecl,
getter: getDecl === undefined
? undefined
: {
name: ts_utils_1.tsUtils.node.getName(getDecl),
decl: getDecl,
constant: this.hasConstant(getDecl),
},
setter: setDecl === undefined
? undefined
: {
name: utils_2.getSetterName(ts_utils_1.tsUtils.node.getName(setDecl)),
decl: setDecl,
},
isPublic,
propertyType: type,
};
}
let callSignatures = type.getCallSignatures();
if (typescript_1.default.isMethodDeclaration(decl) || (typescript_1.default.isPropertyDeclaration(decl) && callSignatures.length > 0)) {
if (callSignatures.length > 1) {
callSignatures = callSignatures.filter((signature) => !ts_utils_1.tsUtils.modifier.isAbstract(signature.getDeclaration()));
}
if (callSignatures.length > 1) {
this.context.reportError(decl, DiagnosticCode_1.DiagnosticCode.InvalidContractMethod, DiagnosticMessage_1.DiagnosticMessage.InvalidContractMethodMultipleSignatures);
}
if (callSignatures.length === 0) {
return undefined;
}
if (typescript_1.default.isPropertyDeclaration(decl)) {
const initializerProp = ts_utils_1.tsUtils.initializer.getInitializer(decl);
const isReadonlyProp = ts_utils_1.tsUtils.modifier.isReadonly(decl);
if (initializerProp === undefined || ts_utils_1.tsUtils.type_.getCallSignatures(type).length === 0 || !isReadonlyProp) {
this.context.reportError(decl, DiagnosticCode_1.DiagnosticCode.InvalidContractStorageType, DiagnosticMessage_1.DiagnosticMessage.InvalidContractStorageType);
return undefined;
}
}
const callSignature = callSignatures[0];
const send = this.hasSend(decl);
const sendUnsafe = this.hasSendUnsafe(decl);
const receive = this.hasReceive(decl);
const claim = this.hasClaim(decl);
const constant = this.hasConstant(decl);
const isUTXO = send || sendUnsafe || receive || claim;
if (isUTXO && constant) {
const decorator = ts_utils_1.tsUtils.decoratable
.getDecoratorsArray(decl)
.find((dec) => this.isDecorator(dec, constants_2.Decorator.constant));
this.context.reportError(decorator === undefined ? decl : decorator, DiagnosticCode_1.DiagnosticCode.InvalidContractMethod, DiagnosticMessage_1.DiagnosticMessage.InvalidContractMethodConstantNative);
return undefined;
}
const returnType = callSignatures.length >= 1 ? ts_utils_1.tsUtils.signature.getReturnType(callSignature) : undefined;
if (claim && returnType !== undefined && !ts_utils_1.tsUtils.type_.isVoid(returnType)) {
const decorator = ts_utils_1.tsUtils.decoratable
.getDecoratorsArray(decl)
.find((dec) => this.isDecorator(dec, constants_2.Decorator.claim));
this.context.reportError(decorator === undefined ? decl : decorator, DiagnosticCode_1.DiagnosticCode.InvalidContractMethod, DiagnosticMessage_1.DiagnosticMessage.InvalidContractMethodNativeReturn);
return undefined;
}
if (isUTXO && callSignatures.length >= 1) {
const signatureTypes = this.context.analysis.extractSignatureTypes(decl, callSignatures[0]);
if (signatureTypes !== undefined) {
const invalidParams = signatureTypes.paramDecls.filter((param) => {
const paramType = signatureTypes.paramTypes.get(param);
return (paramType !== undefined &&
(types_1.hasForwardValue(this.context, param, paramType) ||
ts_utils_1.tsUtils.type_.hasType(paramType, (tpe) => this.context.builtins.isType(param, tpe, 'ForwardedValue'))));
});
invalidParams.forEach((param) => {
this.context.reportError(param, DiagnosticCode_1.DiagnosticCode.InvalidContractType, DiagnosticMessage_1.DiagnosticMessage.InvalidContractTypeForwardNative);
});
if (invalidParams.length > 0) {
return undefined;
}
}
}
return {
type: 'function',
name: ts_utils_1.tsUtils.node.getName(decl),
classDecl,
symbol: ts_utils_1.tsUtils.symbol.getTarget(symbol),
decl,
send,
sendUnsafe,
receive,
claim,
acceptsClaim: callSignatures.length >= 1 && this.isLastParamClaim(decl, callSignatures[0]),
isPublic,
callSignature,
returnType,
constant,
isAbstract: !ts_utils_1.tsUtils.overload.isImplementation(decl),
};
}
const structuredStorageType = constants_1.STRUCTURED_STORAGE_TYPES.find((testType) => this.context.builtins.isInterface(decl, type, testType));
const isReadonly = ts_utils_1.tsUtils.modifier.isReadonly(decl);
const isAbstract = ts_utils_1.tsUtils.modifier.isAbstract(decl);
const initializer = ts_utils_1.tsUtils.initializer.getInitializer(decl);
if (structuredStorageType !== undefined && (isPublic || isAbstract || !isReadonly || initializer === undefined)) {
this.context.reportError(decl, DiagnosticCode_1.DiagnosticCode.InvalidStructuredStorageFor, DiagnosticMessage_1.DiagnosticMessage.InvalidStructuredStorageForProperty);
return undefined;
}
if (structuredStorageType === undefined && !this.context.analysis.isValidStorageType(decl, type)) {
this.context.reportError(decl, DiagnosticCode_1.DiagnosticCode.InvalidContractStorageType, DiagnosticMessage_1.DiagnosticMessage.InvalidContractStorageType);
return undefined;
}
return {
type: 'property',
symbol: ts_utils_1.tsUtils.symbol.getTarget(symbol),
name: ts_utils_1.tsUtils.node.getName(decl),
classDecl,
decl,
isPublic,
propertyType: type,
isReadonly,
isAbstract,
structuredStorageType,
};
}
hasConstant(decl) {
return this.hasDecorator(decl, constants_2.Decorator.constant);
}
hasSend(decl) {
return this.hasDecorator(decl, constants_2.Decorator.send);
}
hasSendUnsafe(decl) {
return this.hasDecorator(decl, constants_2.Decorator.sendUnsafe);
}
hasReceive(decl) {
return this.hasDecorator(decl, constants_2.Decorator.receive);
}
hasClaim(decl) {
return this.hasDecorator(decl, constants_2.Decorator.claim);
}
hasDecorator(decl, name) {
const decorators = ts_utils_1.tsUtils.decoratable.getDecorators(decl);
return decorators === undefined ? false : decorators.some((decorator) => this.isDecorator(decorator, name));
}
isValidDecorator(decorator) {
return constants_2.DECORATORS_ARRAY.some((valid) => this.isDecorator(decorator, valid));
}
isDecorator(decorator, name) {
return this.context.builtins.isValue(ts_utils_1.tsUtils.expression.getExpression(decorator), name);
}
isLastParamClaim(node, callSignature) {
const signatureTypes = this.context.analysis.extractSignatureTypes(node, callSignature);
if (signatureTypes === undefined) {
return false;
}
if (signatureTypes.paramDecls.length === 0) {
return false;
}
const param = signatureTypes.paramDecls[signatureTypes.paramDecls.length - 1];
const paramType = signatureTypes.paramTypes.get(signatureTypes.paramDecls[signatureTypes.paramDecls.length - 1]);
return paramType !== undefined && this.context.builtins.isInterface(param, paramType, 'ClaimTransaction');
}
hasDeployInfo(contractInfo) {
if (contractInfo.propInfos.some((propInfo) => propInfo.type === 'deploy')) {
return true;
}
const superSmartContract = contractInfo.superSmartContract;
if (superSmartContract === undefined) {
return false;
}
return this.hasDeployInfo(superSmartContract);
}
getApproveUpgradeDecl(contractInfo) {
const propInfo = contractInfo.propInfos.find((info) => info.name === constants_2.ContractPropertyName.approveUpgrade);
if (propInfo !== undefined && propInfo.type === 'function' && ts_utils_1.tsUtils.overload.isImplementation(propInfo.decl)) {
return propInfo.decl;
}
const superSmartContract = contractInfo.superSmartContract;
if (superSmartContract === undefined) {
return undefined;
}
return this.getApproveUpgradeDecl(superSmartContract);
}
}
exports.ContractInfoProcessor = ContractInfoProcessor;
//# sourceMappingURL=ContractInfoProcessor.js.map