@neo-one/smart-contract-compiler
Version:
NEO•ONE TypeScript smart contract compiler.
302 lines (300 loc) • 15.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ABISmartContractProcessor = 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 analysis_1 = require("../analysis");
const DiagnosticCode_1 = require("../DiagnosticCode");
const DiagnosticMessage_1 = require("../DiagnosticMessage");
const utils_2 = require("../utils");
const BOOLEAN_RETURN = { type: 'Boolean' };
const VOID_RETURN = { type: 'Void' };
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_1.utils.notNull));
return lodash_1.default.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: utils_2.getSetterName(propInfo.name),
parameters: [
this.toABIParameter(propInfo.name, propInfo.decl, propInfo.propertyType, false, {
error: false,
}),
].filter(utils_1.utils.notNull),
returnType: VOID_RETURN,
},
].filter(utils_1.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_1.utils.notNull),
returnType: VOID_RETURN,
},
].filter(utils_1.utils.notNull);
default:
utils_1.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_1.utils.notNull);
}
processEvents() {
const createEventNotifierDecl = ts_utils_1.tsUtils.symbol.getDeclarations(this.context.builtins.getValueSymbol('createEventNotifier'))[0];
const declareEventDecl = ts_utils_1.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 (typescript_1.default.isIdentifier(node)) {
const parent = ts_utils_1.tsUtils.node.getParent(node);
if (typescript_1.default.isCallExpression(parent)) {
return parent;
}
}
return undefined;
})
.filter(utils_1.utils.notNull);
return calls.reduce((events, call) => {
const event = this.toABIEvent(call, events);
return event === undefined ? events : [...events, event];
}, []);
}
toABIEvent(call, events) {
const callArguments = ts_utils_1.tsUtils.argumented.getArguments(call);
const parent = ts_utils_1.tsUtils.node.getParent(call);
let typeArguments = ts_utils_1.tsUtils.argumented
.getTypeArgumentsArray(call)
.map((typeNode) => this.context.analysis.getType(typeNode));
if (typescript_1.default.isPropertyDeclaration(parent)) {
const propertyName = ts_utils_1.tsUtils.node.getName(parent);
const smartContractType = this.context.analysis.getType(this.contractInfo.smartContract);
if (smartContractType !== undefined) {
const member = ts_utils_1.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 (!typescript_1.default.isStringLiteral(nameArg)) {
this.context.reportError(nameArg, DiagnosticCode_1.DiagnosticCode.InvalidContractEvent, DiagnosticMessage_1.DiagnosticMessage.InvalidContractEventNameStringLiteral);
return undefined;
}
const name = ts_utils_1.tsUtils.literal.getLiteralValue(nameArg);
const parameters = lodash_1.default.zip(callArguments.slice(1), typeArguments)
.map(([paramNameArg, paramType]) => {
if (paramNameArg === undefined || paramType === undefined) {
return undefined;
}
if (!typescript_1.default.isStringLiteral(paramNameArg)) {
this.context.reportError(paramNameArg, DiagnosticCode_1.DiagnosticCode.InvalidContractEvent, DiagnosticMessage_1.DiagnosticMessage.InvalidContractEventArgStringLiteral);
return undefined;
}
const paramName = ts_utils_1.tsUtils.literal.getLiteralValue(paramNameArg);
const param = this.toABIParameter(paramName, paramNameArg, paramType);
if (param !== undefined && param.type === 'ForwardValue') {
this.context.reportError(paramNameArg, DiagnosticCode_1.DiagnosticCode.InvalidContractType, DiagnosticMessage_1.DiagnosticMessage.InvalidContractType);
return undefined;
}
return param;
})
.filter(utils_1.utils.notNull);
const event = { name, parameters };
const dupeEvent = events.find((otherEvent) => otherEvent.name === event.name && !lodash_1.default.isEqual(event, otherEvent));
if (dupeEvent === undefined) {
return event;
}
this.context.reportError(nameArg, DiagnosticCode_1.DiagnosticCode.InvalidContractEvent, DiagnosticMessage_1.DiagnosticMessage.InvalidContractEventDuplicate);
return undefined;
}
paramToABIParameter(param) {
const decls = ts_utils_1.tsUtils.symbol.getDeclarations(param);
const decl = utils_1.utils.nullthrows(decls[0]);
const initializer = ts_utils_1.tsUtils.initializer.getInitializer(decl);
const parameter = this.toABIParameter(ts_utils_1.tsUtils.symbol.getName(param), decl, this.getParamSymbolType(param), initializer !== undefined);
if (parameter === undefined ||
initializer === undefined ||
(!typescript_1.default.isPropertyAccessExpression(initializer) && !typescript_1.default.isCallExpression(initializer))) {
return parameter;
}
if (typescript_1.default.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 Object.assign(Object.assign({}, 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(ts_utils_1.tsUtils.symbol.getDeclarations(lastParam)[0], lastParamType);
}
getParamSymbolType(param) {
const decls = ts_utils_1.tsUtils.symbol.getDeclarations(param);
const decl = utils_1.utils.nullthrows(decls[0]);
return this.context.analysis.getTypeOfSymbol(param, decl);
}
toABIParameter(nameIn, node, resolvedTypeIn, optional = false, options = analysis_1.DEFAULT_DIAGNOSTIC_OPTIONS) {
const name = nameIn.startsWith('_') ? nameIn.slice(1) : nameIn;
let resolvedType = resolvedTypeIn;
if (typescript_1.default.isParameter(node) && ts_utils_1.tsUtils.parameter.isRestParameter(node) && resolvedType !== undefined) {
resolvedType = ts_utils_1.tsUtils.type_.getTypeArgumentsArray(resolvedType)[0];
}
const type = utils_2.toABIReturn(this.context, node, resolvedType, optional, options);
if (type === undefined) {
return undefined;
}
if (typescript_1.default.isParameter(node) && ts_utils_1.tsUtils.parameter.isRestParameter(node)) {
return Object.assign(Object.assign({}, type), { name, rest: true });
}
return Object.assign(Object.assign({}, type), { name });
}
toABIReturn(node, resolvedType, optional = false, options = analysis_1.DEFAULT_DIAGNOSTIC_OPTIONS) {
const type = utils_2.toABIReturn(this.context, node, resolvedType, optional, options);
return type === undefined ? VOID_RETURN : type;
}
}
exports.ABISmartContractProcessor = ABISmartContractProcessor;
//# sourceMappingURL=ABISmartContractProcessor.js.map