/* This file is part of ethereum.js. ethereum.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. ethereum.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ /** @file abi.js * @authors: * Marek Kotewicz * Gav Wood * @date 2014 */ var utils = require('./utils'); var types = require('./types'); var c = require('./const'); var f = require('./formatters'); var displayTypeError = function (type) { console.error('parser does not support type: ' + type); }; /// This method should be called if we want to check if givent type is an array type /// @returns true if it is, otherwise false var arrayType = function (type) { return type.slice(-2) === '[]'; }; var dynamicTypeBytes = function (type, value) { // TODO: decide what to do with array of strings if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. return f.formatInputInt(value.length); return ""; }; var inputTypes = types.inputTypes(); /// Formats input params to bytes /// @param abi contract method inputs /// @param array of params that will be formatted to bytes /// @returns bytes representation of input params var formatInput = function (inputs, params) { var bytes = ""; var toAppendConstant = ""; var toAppendArrayContent = ""; /// first we iterate in search for dynamic inputs.forEach(function (input, index) { bytes += dynamicTypeBytes(input.type, params[index]); }); inputs.forEach(function (input, i) { var typeMatch = false; for (var j = 0; j < inputTypes.length && !typeMatch; j++) { typeMatch = inputTypes[j].type(inputs[i].type, params[i]); } if (!typeMatch) { displayTypeError(inputs[i].type); } var formatter = inputTypes[j - 1].format; if (arrayType(inputs[i].type)) toAppendArrayContent += params[i].reduce(function (acc, curr) { return acc + formatter(curr); }, ""); else if (inputs[i].type === 'string') toAppendArrayContent += formatter(params[i]); else toAppendConstant += formatter(params[i]); }); bytes += toAppendConstant + toAppendArrayContent; return bytes; }; var dynamicBytesLength = function (type) { if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. return c.ETH_PADDING * 2; return 0; }; var outputTypes = types.outputTypes(); /// Formats output bytes back to param list /// @param contract abi method outputs /// @param bytes representtion of output /// @returns array of output params var formatOutput = function (outs, output) { output = output.slice(2); var result = []; var padding = c.ETH_PADDING * 2; var dynamicPartLength = outs.reduce(function (acc, curr) { return acc + dynamicBytesLength(curr.type); }, 0); var dynamicPart = output.slice(0, dynamicPartLength); output = output.slice(dynamicPartLength); outs.forEach(function (out, i) { /*jshint maxcomplexity:6 */ var typeMatch = false; for (var j = 0; j < outputTypes.length && !typeMatch; j++) { typeMatch = outputTypes[j].type(outs[i].type); } if (!typeMatch) { displayTypeError(outs[i].type); } var formatter = outputTypes[j - 1].format; if (arrayType(outs[i].type)) { var size = f.formatOutputUInt(dynamicPart.slice(0, padding)); dynamicPart = dynamicPart.slice(padding); var array = []; for (var k = 0; k < size; k++) { array.push(formatter(output.slice(0, padding))); output = output.slice(padding); } result.push(array); } else if (types.prefixedType('string')(outs[i].type)) { dynamicPart = dynamicPart.slice(padding); result.push(formatter(output.slice(0, padding))); output = output.slice(padding); } else { result.push(formatter(output.slice(0, padding))); output = output.slice(padding); } }); return result; }; /// @param json abi for contract /// @returns input parser object for given json abi /// TODO: refactor creating the parser, do not double logic from contract var inputParser = function (json) { var parser = {}; json.forEach(function (method) { var displayName = utils.extractDisplayName(method.name); var typeName = utils.extractTypeName(method.name); var impl = function () { var params = Array.prototype.slice.call(arguments); return formatInput(method.inputs, params); }; if (parser[displayName] === undefined) { parser[displayName] = impl; } parser[displayName][typeName] = impl; }); return parser; }; /// @param json abi for contract /// @returns output parser for given json abi var outputParser = function (json) { var parser = {}; json.forEach(function (method) { var displayName = utils.extractDisplayName(method.name); var typeName = utils.extractTypeName(method.name); var impl = function (output) { return formatOutput(method.outputs, output); }; if (parser[displayName] === undefined) { parser[displayName] = impl; } parser[displayName][typeName] = impl; }); return parser; }; module.exports = { inputParser: inputParser, outputParser: outputParser, formatInput: formatInput, formatOutput: formatOutput };