UNPKG

@neo-one/smart-contract-compiler

Version:

NEO•ONE TypeScript smart contract compiler.

297 lines (295 loc) 14.4 kB
import { tsUtils } from '@neo-one/ts-utils'; import { utils } from '@neo-one/utils'; import _ from 'lodash'; import ts from 'typescript'; import { DEFAULT_DIAGNOSTIC_OPTIONS } from '../analysis'; import { DiagnosticCode } from '../DiagnosticCode'; import { DiagnosticMessage } from '../DiagnosticMessage'; import { getSetterName, toABIReturn } from '../utils'; const BOOLEAN_RETURN = { type: 'Boolean' }; const VOID_RETURN = { type: 'Void' }; export class ABISmartContractProcessor { constructor(context, contractInfo) { this.context = context; this.contractInfo = contractInfo; } process() { return { functions: this.processFunctions(), events: this.processEvents(), }; } processFunctions() { const deployInfo = this.findDeployInfo(); const propInfos = this.contractInfo.propInfos .filter((propInfo) => propInfo.isPublic && propInfo.type !== 'deploy') .concat([deployInfo].filter(utils.notNull)); return _.flatten(propInfos.map((propInfo) => { switch (propInfo.type) { case 'deploy': return [ { name: propInfo.name, parameters: propInfo.isMixinDeploy ? [] : this.getParameters({ callSignature: propInfo.callSignature }), returnType: BOOLEAN_RETURN, }, ]; case 'refundAssets': return [ { name: propInfo.name, sendUnsafe: true, parameters: [], returnType: VOID_RETURN, }, ]; case 'completeSend': return [ { name: propInfo.name, completeSend: true, parameters: [], returnType: VOID_RETURN, }, ]; case 'upgrade': return [ { name: propInfo.name, parameters: [ { name: 'script', type: 'Buffer' }, { name: 'parameterList', type: 'Buffer' }, { name: 'returnType', type: 'Integer', decimals: 0 }, { name: 'properties', type: 'Integer', decimals: 0 }, { name: 'contractName', type: 'String' }, { name: 'codeVersion', type: 'String' }, { name: 'author', type: 'String' }, { name: 'email', type: 'String' }, { name: 'description', type: 'String' }, ], returnType: VOID_RETURN, }, ]; case 'function': return [ { name: propInfo.name, parameters: this.getParameters({ callSignature: propInfo.callSignature, send: propInfo.send, claim: propInfo.claim, }), returnType: this.toABIReturn(propInfo.decl, propInfo.returnType), constant: propInfo.constant, send: propInfo.send, sendUnsafe: propInfo.sendUnsafe, receive: propInfo.receive, claim: propInfo.claim, }, ]; case 'property': return [ { name: propInfo.name, parameters: [], returnType: this.toABIReturn(propInfo.decl, propInfo.propertyType), constant: true, }, propInfo.isReadonly ? undefined : { name: getSetterName(propInfo.name), parameters: [ this.toABIParameter(propInfo.name, propInfo.decl, propInfo.propertyType, false, { error: false, }), ].filter(utils.notNull), returnType: VOID_RETURN, }, ].filter(utils.notNull); case 'accessor': return [ propInfo.getter === undefined ? undefined : { name: propInfo.getter.name, parameters: [], constant: propInfo.getter.constant, returnType: this.toABIReturn(propInfo.getter.decl, propInfo.propertyType), }, propInfo.setter === undefined ? undefined : { name: propInfo.setter.name, parameters: [ this.toABIParameter(propInfo.name, propInfo.getter === undefined ? propInfo.setter.decl : propInfo.getter.decl, propInfo.propertyType, false, propInfo.getter === undefined ? { error: true } : undefined), ].filter(utils.notNull), returnType: VOID_RETURN, }, ].filter(utils.notNull); default: utils.assertNever(propInfo); throw new Error('For TS'); } })); } findDeployInfo(contractInfo = this.contractInfo) { const deployInfo = contractInfo.propInfos.find((propInfo) => propInfo.type === 'deploy'); const superSmartContract = contractInfo.superSmartContract; if (deployInfo !== undefined) { if (deployInfo.decl === undefined && superSmartContract !== undefined) { const superDeployInfo = this.findDeployInfo(superSmartContract); return superDeployInfo === undefined ? deployInfo : superDeployInfo; } return deployInfo; } return superSmartContract === undefined ? undefined : this.findDeployInfo(superSmartContract); } getParameters({ callSignature, claim = false, send = false, }) { if (callSignature === undefined) { return []; } let parameters = callSignature.getParameters(); if (claim && this.checkLastParam(parameters, 'ClaimTransaction')) { parameters = parameters.slice(0, -1); } if (send && this.checkLastParam(parameters, 'Transfer')) { parameters = parameters.slice(0, -1); } return parameters.map((parameter) => this.paramToABIParameter(parameter)).filter(utils.notNull); } processEvents() { const createEventNotifierDecl = tsUtils.symbol.getDeclarations(this.context.builtins.getValueSymbol('createEventNotifier'))[0]; const declareEventDecl = tsUtils.symbol.getDeclarations(this.context.builtins.getValueSymbol('declareEvent'))[0]; const calls = this.context.analysis .findReferencesAsNodes(createEventNotifierDecl) .concat(this.context.analysis.findReferencesAsNodes(declareEventDecl)) .map((node) => { if (ts.isIdentifier(node)) { const parent = tsUtils.node.getParent(node); if (ts.isCallExpression(parent)) { return parent; } } return undefined; }) .filter(utils.notNull); return calls.reduce((events, call) => { const event = this.toABIEvent(call, events); return event === undefined ? events : [...events, event]; }, []); } toABIEvent(call, events) { const callArguments = tsUtils.argumented.getArguments(call); const parent = tsUtils.node.getParent(call); let typeArguments = tsUtils.argumented .getTypeArgumentsArray(call) .map((typeNode) => this.context.analysis.getType(typeNode)); if (ts.isPropertyDeclaration(parent)) { const propertyName = tsUtils.node.getName(parent); const smartContractType = this.context.analysis.getType(this.contractInfo.smartContract); if (smartContractType !== undefined) { const member = tsUtils.type_.getProperty(smartContractType, propertyName); if (member !== undefined) { const type = this.context.analysis.getTypeOfSymbol(member, this.contractInfo.smartContract); const signatureTypes = this.context.analysis.extractSignatureForType(call, type); if (signatureTypes !== undefined) { typeArguments = signatureTypes.paramDecls.map((paramDecl) => signatureTypes.paramTypes.get(paramDecl)); } } } } const nameArg = callArguments[0]; if (nameArg === undefined) { return undefined; } if (!ts.isStringLiteral(nameArg)) { this.context.reportError(nameArg, DiagnosticCode.InvalidContractEvent, DiagnosticMessage.InvalidContractEventNameStringLiteral); return undefined; } const name = tsUtils.literal.getLiteralValue(nameArg); const parameters = _.zip(callArguments.slice(1), typeArguments) .map(([paramNameArg, paramType]) => { if (paramNameArg === undefined || paramType === undefined) { return undefined; } if (!ts.isStringLiteral(paramNameArg)) { this.context.reportError(paramNameArg, DiagnosticCode.InvalidContractEvent, DiagnosticMessage.InvalidContractEventArgStringLiteral); return undefined; } const paramName = tsUtils.literal.getLiteralValue(paramNameArg); const param = this.toABIParameter(paramName, paramNameArg, paramType); if (param !== undefined && param.type === 'ForwardValue') { this.context.reportError(paramNameArg, DiagnosticCode.InvalidContractType, DiagnosticMessage.InvalidContractType); return undefined; } return param; }) .filter(utils.notNull); const event = { name, parameters }; const dupeEvent = events.find((otherEvent) => otherEvent.name === event.name && !_.isEqual(event, otherEvent)); if (dupeEvent === undefined) { return event; } this.context.reportError(nameArg, DiagnosticCode.InvalidContractEvent, DiagnosticMessage.InvalidContractEventDuplicate); return undefined; } paramToABIParameter(param) { const decls = tsUtils.symbol.getDeclarations(param); const decl = utils.nullthrows(decls[0]); const initializer = tsUtils.initializer.getInitializer(decl); const parameter = this.toABIParameter(tsUtils.symbol.getName(param), decl, this.getParamSymbolType(param), initializer !== undefined); if (parameter === undefined || initializer === undefined || (!ts.isPropertyAccessExpression(initializer) && !ts.isCallExpression(initializer))) { return parameter; } if (ts.isPropertyAccessExpression(initializer)) { const symbol = this.context.analysis.getSymbol(initializer); const senderAddress = this.context.builtins.getOnlyMemberSymbol('DeployConstructor', 'senderAddress'); if (symbol === senderAddress) { const sender = { type: 'sender' }; return { ...parameter, default: sender }; } } return parameter; } checkLastParam(parameters, value) { return this.checkLastParamBase(parameters, (decl, type) => this.context.builtins.isInterface(decl, type, value)); } checkLastParamBase(parameters, checkParamType) { if (parameters.length === 0) { return false; } const lastParam = parameters[parameters.length - 1]; const lastParamType = this.getParamSymbolType(lastParam); return lastParamType !== undefined && checkParamType(tsUtils.symbol.getDeclarations(lastParam)[0], lastParamType); } getParamSymbolType(param) { const decls = tsUtils.symbol.getDeclarations(param); const decl = utils.nullthrows(decls[0]); return this.context.analysis.getTypeOfSymbol(param, decl); } toABIParameter(nameIn, node, resolvedTypeIn, optional = false, options = DEFAULT_DIAGNOSTIC_OPTIONS) { const name = nameIn.startsWith('_') ? nameIn.slice(1) : nameIn; let resolvedType = resolvedTypeIn; if (ts.isParameter(node) && tsUtils.parameter.isRestParameter(node) && resolvedType !== undefined) { resolvedType = tsUtils.type_.getTypeArgumentsArray(resolvedType)[0]; } const type = toABIReturn(this.context, node, resolvedType, optional, options); if (type === undefined) { return undefined; } if (ts.isParameter(node) && tsUtils.parameter.isRestParameter(node)) { return { ...type, name, rest: true }; } return { ...type, name }; } toABIReturn(node, resolvedType, optional = false, options = DEFAULT_DIAGNOSTIC_OPTIONS) { const type = toABIReturn(this.context, node, resolvedType, optional, options); return type === undefined ? VOID_RETURN : type; } } //# sourceMappingURL=ABISmartContractProcessor.js.map