/* 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('./config'); var f = require('./formatters'); /** * throw incorrect type error * * @method throwTypeError * @param {String} type * @throws incorrect type error */ var throwTypeError = function (type) { throw new Error('parser does not support type: ' + type); }; /** This method should be called if we want to check if givent type is an array type * * @method isArrayType * @param {String} type name * @returns {Boolean} true if it is, otherwise false */ var isArrayType = function (type) { return type.slice(-2) === '[]'; }; /** * This method should be called to return dynamic type length in hex * * @method dynamicTypeBytes * @param {String} type * @param {String|Array} dynamic type * @return {String} length of dynamic type in hex or empty string if type is not dynamic */ var dynamicTypeBytes = function (type, value) { // TODO: decide what to do with array of strings if (isArrayType(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 * * @method formatInput * @param {Array} abi inputs of method * @param {Array} 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) { /*jshint maxcomplexity:5 */ var typeMatch = false; for (var j = 0; j < inputTypes.length && !typeMatch; j++) { typeMatch = inputTypes[j].type(inputs[i].type, params[i]); } if (!typeMatch) { throwTypeError(inputs[i].type); } var formatter = inputTypes[j - 1].format; if (isArrayType(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; }; /** * This method should be called to predict the length of dynamic type * * @method dynamicBytesLength * @param {String} type * @returns {Number} length of dynamic type, 0 or multiplication of ETH_PADDING (32) */ var dynamicBytesLength = function (type) { if (isArrayType(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 * * @method formatOutput * @param {Array} abi outputs of method * @param {String} bytes represention of output * @returns {Array} 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) { throwTypeError(outs[i].type); } var formatter = outputTypes[j - 1].format; if (isArrayType(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; }; /** * Should be called to create input parser for contract with given abi * * @method inputParser * @param {Array} contract abi * @returns {Object} 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; }; /** * Should be called to create output parser for contract with given abi * * @method outputParser * @param {Array} contract abi * @returns {Object} 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 };