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