/* 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 coder.js * @author Marek Kotewicz * @date 2015 */ var f = require('./formatters'); var SolidityTypeAddress = require('./address'); var SolidityTypeBool = require('./bool'); var SolidityTypeInt = require('./int'); var SolidityTypeUInt = require('./uint'); var SolidityTypeDynamicBytes = require('./dynamicbytes'); var SolidityTypeString = require('./string'); var SolidityTypeReal = require('./real'); var SolidityTypeUReal = require('./ureal'); var SolidityTypeBytes = require('./bytes'); /** * SolidityCoder prototype should be used to encode/decode solidity params of any type */ var SolidityCoder = function (types) { this._types = types; }; /** * This method should be used to transform type to SolidityType * * @method _requireType * @param {String} type * @returns {SolidityType} * @throws {Error} throws if no matching type is found */ SolidityCoder.prototype._requireType = function (type) { var solidityType = this._types.filter(function (t) { return t.isType(type); })[0]; if (!solidityType) { throw Error('invalid solidity type!: ' + type); } return solidityType; }; /** * Should be used to encode plain param * * @method encodeParam * @param {String} type * @param {Object} plain param * @return {String} encoded plain param */ SolidityCoder.prototype.encodeParam = function (type, param) { return this.encodeParams([type], [param]); }; /** * Should be used to encode list of params * * @method encodeParams * @param {Array} types * @param {Array} params * @return {String} encoded list of params */ SolidityCoder.prototype.encodeParams = function (types, params) { var solidityTypes = this.getSolidityTypes(types); var encodeds = solidityTypes.map(function (solidityType, index) { return solidityType.encode(params[index], types[index]); }); var dynamicOffset = solidityTypes.reduce(function (acc, solidityType, index) { return acc + solidityType.staticPartLength(types[index]); }, 0); var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); return result; }; SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, encodeds, dynamicOffset) { var result = ""; var self = this; var isDynamic = function (i) { return solidityTypes[i].isDynamicArray(types[i]) || solidityTypes[i].isDynamicType(types[i]); }; types.forEach(function (type, i) { if (isDynamic(i)) { result += f.formatInputInt(dynamicOffset).encode(); var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); dynamicOffset += e.length / 2; } else { // don't add length to dynamicOffset. it's already counted result += self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); } // TODO: figure out nested arrays }); types.forEach(function (type, i) { if (isDynamic(i)) { var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); dynamicOffset += e.length / 2; result += e; } }); return result; }; // TODO: refactor whole encoding! SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded, offset) { var self = this; if (solidityType.isDynamicArray(type)) { return (function () { // offset was already set var nestedName = solidityType.nestedName(type); var nestedStaticPartLength = solidityType.staticPartLength(nestedName); var result = encoded[0]; (function () { var previousLength = 2; // in int if (solidityType.isDynamicArray(nestedName)) { for (var i = 1; i < encoded.length; i++) { previousLength += +(encoded[i - 1])[0] || 0; result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); } } })(); // first element is length, skip it (function () { for (var i = 0; i < encoded.length - 1; i++) { var additionalOffset = result / 2; result += self.encodeWithOffset(nestedName, solidityType, encoded[i + 1], offset + additionalOffset); } })(); return result; })(); } else if (solidityType.isStaticArray(type)) { return (function () { var nestedName = solidityType.nestedName(type); var nestedStaticPartLength = solidityType.staticPartLength(nestedName); var result = ""; if (solidityType.isDynamicArray(nestedName)) { (function () { var previousLength = 0; // in int for (var i = 0; i < encoded.length; i++) { // calculate length of previous item previousLength += +(encoded[i - 1] || [])[0] || 0; result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); } })(); } (function () { for (var i = 0; i < encoded.length; i++) { var additionalOffset = result / 2; result += self.encodeWithOffset(nestedName, solidityType, encoded[i], offset + additionalOffset); } })(); return result; })(); } return encoded; }; /** * Should be used to decode bytes to plain param * * @method decodeParam * @param {String} type * @param {String} bytes * @return {Object} plain param */ SolidityCoder.prototype.decodeParam = function (type, bytes) { return this.decodeParams([type], bytes)[0]; }; /** * Should be used to decode list of params * * @method decodeParam * @param {Array} types * @param {String} bytes * @return {Array} array of plain params */ SolidityCoder.prototype.decodeParams = function (types, bytes) { var solidityTypes = this.getSolidityTypes(types); var offsets = this.getOffsets(types, solidityTypes); return solidityTypes.map(function (solidityType, index) { return solidityType.decode(bytes, offsets[index], types[index], index); }); }; SolidityCoder.prototype.getOffsets = function (types, solidityTypes) { var lengths = solidityTypes.map(function (solidityType, index) { return solidityType.staticPartLength(types[index]); // get length }); for (var i = 0; i < lengths.length; i++) { // sum with length of previous element var previous = (lengths[i - 1] || 0); lengths[i] += previous; } return lengths.map(function (length, index) { // remove the current length, so the length is sum of previous elements return length - solidityTypes[index].staticPartLength(types[index]); }); }; SolidityCoder.prototype.getSolidityTypes = function (types) { var self = this; return types.map(function (type) { return self._requireType(type); }); }; var coder = new SolidityCoder([ new SolidityTypeAddress(), new SolidityTypeBool(), new SolidityTypeInt(), new SolidityTypeUInt(), new SolidityTypeDynamicBytes(), new SolidityTypeBytes(), new SolidityTypeString(), new SolidityTypeReal(), new SolidityTypeUReal() ]); module.exports = coder;