UNPKG

web3quorum

Version:

Ethereum JavaScript API, middleware to talk to a ethereum node over RPC

248 lines (207 loc) 8 kB
/* This file is part of web3.js. web3.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. web3.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 web3.js. If not, see <http://www.gnu.org/licenses/>. */ /** * @file coder.js * @author Marek Kotewicz <marek@ethdev.com> * @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'); var isDynamic = function (solidityType, type) { return solidityType.isDynamicType(type) || solidityType.isDynamicArray(type); }; /** * 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) { var staticPartLength = solidityType.staticPartLength(types[index]); var roundedStaticPartLength = Math.floor((staticPartLength + 31) / 32) * 32; return acc + (isDynamic(solidityTypes[index], types[index]) ? 32 : roundedStaticPartLength); }, 0); var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset); return result; }; SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, encodeds, dynamicOffset) { var result = ""; var self = this; types.forEach(function (type, i) { if (isDynamic(solidityTypes[i], types[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(solidityTypes[i], types[i])) { var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset); dynamicOffset += e.length / 2; result += e; } }); return result; }; SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded, offset) { /* jshint maxcomplexity: 17 */ /* jshint maxdepth: 5 */ var self = this; var encodingMode={dynamic:1,static:2,other:3}; var mode=(solidityType.isDynamicArray(type)?encodingMode.dynamic:(solidityType.isStaticArray(type)?encodingMode.static:encodingMode.other)); if(mode !== encodingMode.other){ var nestedName = solidityType.nestedName(type); var nestedStaticPartLength = solidityType.staticPartLength(nestedName); var result = (mode === encodingMode.dynamic ? encoded[0] : ''); if (solidityType.isDynamicArray(nestedName)) { var previousLength = (mode === encodingMode.dynamic ? 2 : 0); for (var i = 0; i < encoded.length; i++) { // calculate length of previous item if(mode === encodingMode.dynamic){ previousLength += +(encoded[i - 1])[0] || 0; } else if(mode === encodingMode.static){ previousLength += +(encoded[i - 1] || [])[0] || 0; } result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode(); } } var len= (mode === encodingMode.dynamic ? encoded.length-1 : encoded.length); for (var c = 0; c < len; c++) { var additionalOffset = result / 2; if(mode === encodingMode.dynamic){ result += self.encodeWithOffset(nestedName, solidityType, encoded[c + 1], offset + additionalOffset); } else if(mode === encodingMode.static){ result += self.encodeWithOffset(nestedName, solidityType, encoded[c], 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]); }); for (var i = 1; i < lengths.length; i++) { // sum with length of previous element lengths[i] += lengths[i - 1]; } return lengths.map(function (length, index) { // remove the current length, so the length is sum of previous elements var staticPartLength = solidityTypes[index].staticPartLength(types[index]); return length - staticPartLength; }); }; 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;