2015-01-31 02:54:17 +01:00
|
|
|
/*
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
/**
|
|
|
|
* @file coder.js
|
|
|
|
* @author Marek Kotewicz <marek@ethdev.com>
|
2015-01-31 02:54:17 +01:00
|
|
|
* @date 2015
|
|
|
|
*/
|
|
|
|
|
2015-04-16 16:37:13 +02:00
|
|
|
var BigNumber = require('bignumber.js');
|
|
|
|
var utils = require('../utils/utils');
|
2015-04-20 10:36:11 +02:00
|
|
|
var SolidityParam = require('./param');
|
2015-07-28 03:44:19 +02:00
|
|
|
var f = require('./formatters');
|
2015-01-31 02:54:17 +01:00
|
|
|
|
2015-07-28 03:44:19 +02:00
|
|
|
var SolidityType = require('./type');
|
|
|
|
var SolidityTypeAddress = require('./address');
|
2015-04-16 16:37:13 +02:00
|
|
|
|
2015-04-20 11:20:05 +02:00
|
|
|
/**
|
|
|
|
* SolidityCoder prototype should be used to encode/decode solidity params of any type
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
var SolidityCoder = function (types) {
|
|
|
|
this._types = types;
|
2015-04-16 16:37:13 +02:00
|
|
|
};
|
|
|
|
|
2015-04-20 11:20:05 +02:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
SolidityCoder.prototype._requireType = function (type) {
|
|
|
|
var solidityType = this._types.filter(function (t) {
|
2015-04-16 16:37:13 +02:00
|
|
|
return t.isType(type);
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
if (!solidityType) {
|
|
|
|
throw Error('invalid solidity type!: ' + type);
|
|
|
|
}
|
|
|
|
|
2015-04-16 21:16:26 +02:00
|
|
|
return solidityType;
|
|
|
|
};
|
|
|
|
|
2015-04-20 11:20:05 +02:00
|
|
|
/**
|
|
|
|
* Should be used to encode plain param
|
|
|
|
*
|
|
|
|
* @method encodeParam
|
|
|
|
* @param {String} type
|
|
|
|
* @param {Object} plain param
|
|
|
|
* @return {String} encoded plain param
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
SolidityCoder.prototype.encodeParam = function (type, param) {
|
2015-07-28 00:14:41 +02:00
|
|
|
return this.encodeParams([type], [param]);
|
2015-04-16 21:16:26 +02:00
|
|
|
};
|
|
|
|
|
2015-04-20 11:20:05 +02:00
|
|
|
/**
|
|
|
|
* Should be used to encode list of params
|
|
|
|
*
|
|
|
|
* @method encodeParams
|
|
|
|
* @param {Array} types
|
|
|
|
* @param {Array} params
|
|
|
|
* @return {String} encoded list of params
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
SolidityCoder.prototype.encodeParams = function (types, params) {
|
2015-07-28 00:14:41 +02:00
|
|
|
var solidityTypes = this.getSolidityTypes(types);
|
|
|
|
var offsets = this.getOffsets(types, solidityTypes);
|
|
|
|
|
2015-07-28 01:36:32 +02:00
|
|
|
var encodeds = solidityTypes.map(function (solidityType, index) {
|
2015-07-28 00:14:41 +02:00
|
|
|
return solidityType.encode(params[index], types[index]);
|
2015-05-09 19:05:34 +02:00
|
|
|
});
|
|
|
|
|
2015-07-28 00:32:46 +02:00
|
|
|
var totalOffset = solidityTypes.reduce(function (acc, solidityType, index) {
|
|
|
|
return acc + solidityType.staticPartLength(types[index]);
|
|
|
|
}, 0);
|
|
|
|
|
2015-07-28 02:34:31 +02:00
|
|
|
var self = this;
|
2015-07-28 00:32:46 +02:00
|
|
|
var result = solidityTypes.reduce(function (acc, solidityType, index) {
|
|
|
|
if (solidityType.isDynamicArray(types[index])) {
|
2015-07-28 01:36:32 +02:00
|
|
|
return acc + f.formatInputInt(totalOffset + offsets[index]).encode();
|
|
|
|
} else if (solidityType.isStaticArray(types[index])) {
|
|
|
|
var offset = acc.length / 2;
|
2015-07-28 02:34:31 +02:00
|
|
|
return acc + self.encodeWithOffset(types[index], solidityType, encodeds[index], totalOffset + offset);
|
2015-07-28 00:32:46 +02:00
|
|
|
}
|
2015-07-28 01:36:32 +02:00
|
|
|
return acc + encodeds[index];
|
2015-07-28 00:32:46 +02:00
|
|
|
}, "");
|
|
|
|
|
2015-07-28 01:43:28 +02:00
|
|
|
result = solidityTypes.reduce(function (acc, solidityType, index) {
|
|
|
|
if (solidityType.isDynamicArray(types[index])) {
|
|
|
|
var offset = acc.length / 2;
|
|
|
|
return acc + self.encodeWithOffset(types[index], solidityType, encodeds[index], offset);
|
|
|
|
}
|
|
|
|
return acc;
|
2015-07-28 01:36:32 +02:00
|
|
|
}, result);
|
|
|
|
|
2015-07-28 00:32:46 +02:00
|
|
|
return result;
|
2015-04-16 21:16:26 +02:00
|
|
|
};
|
|
|
|
|
2015-07-28 01:36:32 +02:00
|
|
|
SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded, offset) {
|
|
|
|
if (solidityType.isDynamicArray(type)) {
|
|
|
|
// offset was already set
|
|
|
|
var nestedName = solidityType.nestedName(type);
|
|
|
|
var nestedStaticPartLength = solidityType.staticPartLength(nestedName);
|
|
|
|
var result = encoded[0];
|
|
|
|
|
2015-07-28 03:30:47 +02:00
|
|
|
var previousLength = 2; // in int
|
2015-07-28 01:36:32 +02:00
|
|
|
if (solidityType.isDynamicArray(nestedName)) {
|
2015-07-28 03:30:47 +02:00
|
|
|
for (var i = 1; i < encoded.length; i++) {
|
|
|
|
previousLength += +(encoded[i - 1] || {})[0] || 0;
|
|
|
|
result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode();
|
2015-07-28 01:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// first element is length, skip it
|
|
|
|
for (var i = 0; i < encoded.length - 1; i++) {
|
2015-07-28 03:30:47 +02:00
|
|
|
var additionalOffset = result / 2;
|
|
|
|
result += this.encodeWithOffset(nestedName, solidityType, encoded[i + 1], offset + additionalOffset);
|
2015-07-28 01:36:32 +02:00
|
|
|
}
|
2015-07-28 00:32:46 +02:00
|
|
|
|
2015-07-28 01:36:32 +02:00
|
|
|
return result;
|
|
|
|
|
|
|
|
} else if (solidityType.isStaticArray(type)) {
|
|
|
|
var nestedName = solidityType.nestedName(type);
|
|
|
|
var nestedStaticPartLength = solidityType.staticPartLength(nestedName);
|
|
|
|
var result = "";
|
|
|
|
|
2015-07-28 02:34:31 +02:00
|
|
|
var previousLength = 0; // in int
|
2015-07-28 01:36:32 +02:00
|
|
|
if (solidityType.isDynamicArray(nestedName)) {
|
|
|
|
for (var i = 0; i < encoded.length; i++) {
|
2015-07-28 02:34:31 +02:00
|
|
|
// calculate length of previous item
|
2015-07-28 03:30:47 +02:00
|
|
|
previousLength += +(encoded[i - 1] || {})[0] || 0;
|
2015-07-28 02:34:31 +02:00
|
|
|
result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode();
|
2015-07-28 01:36:32 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (var i = 0; i < encoded.length; i++) {
|
2015-07-28 02:34:31 +02:00
|
|
|
var additionalOffset = result / 2;
|
|
|
|
result += this.encodeWithOffset(nestedName, solidityType, encoded[i], offset + additionalOffset);
|
2015-07-28 01:36:32 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
return encoded;
|
|
|
|
};
|
2015-07-28 00:32:46 +02:00
|
|
|
|
2015-04-20 11:20:05 +02:00
|
|
|
/**
|
|
|
|
* Should be used to decode bytes to plain param
|
|
|
|
*
|
|
|
|
* @method decodeParam
|
|
|
|
* @param {String} type
|
|
|
|
* @param {String} bytes
|
|
|
|
* @return {Object} plain param
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
SolidityCoder.prototype.decodeParam = function (type, bytes) {
|
2015-05-09 20:15:08 +02:00
|
|
|
return this.decodeParams([type], bytes)[0];
|
2015-04-16 21:16:26 +02:00
|
|
|
};
|
|
|
|
|
2015-04-20 11:20:05 +02:00
|
|
|
/**
|
|
|
|
* Should be used to decode list of params
|
|
|
|
*
|
|
|
|
* @method decodeParam
|
|
|
|
* @param {Array} types
|
|
|
|
* @param {String} bytes
|
|
|
|
* @return {Array} array of plain params
|
|
|
|
*/
|
2015-04-16 21:46:07 +02:00
|
|
|
SolidityCoder.prototype.decodeParams = function (types, bytes) {
|
2015-07-28 00:14:41 +02:00
|
|
|
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);
|
2015-07-27 22:38:33 +02:00
|
|
|
});
|
2015-07-28 00:14:41 +02:00
|
|
|
};
|
2015-07-27 22:38:33 +02:00
|
|
|
|
2015-07-28 00:14:41 +02:00
|
|
|
SolidityCoder.prototype.getOffsets = function (types, solidityTypes) {
|
|
|
|
return solidityTypes.map(function (solidityType, index) {
|
2015-07-27 22:38:33 +02:00
|
|
|
return solidityType.staticPartLength(types[index]);
|
|
|
|
// get length
|
|
|
|
}).map(function (length, index, lengths) {
|
|
|
|
// sum with length of previous element
|
|
|
|
return length + (lengths[index - 1] || 0);
|
|
|
|
}).map(function (length, index) {
|
|
|
|
// remove the current length, so the length is sum of previous elements
|
|
|
|
return length - solidityTypes[index].staticPartLength(types[index]);
|
|
|
|
});
|
2015-07-28 00:14:41 +02:00
|
|
|
};
|
|
|
|
|
2015-07-28 02:34:31 +02:00
|
|
|
SolidityCoder.prototype.getOffsetsOfSingleType = function (types, solidityType) {
|
|
|
|
return types.map(function (type) {
|
|
|
|
return solidityType.staticPartLength(type);
|
|
|
|
// get length
|
|
|
|
}).map(function (length, index, lengths) {
|
|
|
|
// sum with length of previous element
|
|
|
|
return length + (lengths[index - 1] || 0);
|
|
|
|
}).map(function (length, index) {
|
|
|
|
// remove the current length, so the length is sum of previous elements
|
|
|
|
return length - solidityType.staticPartLength(types[index]);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-07-28 00:14:41 +02:00
|
|
|
SolidityCoder.prototype.getSolidityTypes = function (types) {
|
|
|
|
var self = this;
|
|
|
|
return types.map(function (type) {
|
|
|
|
return self._requireType(type);
|
2015-04-16 21:16:26 +02:00
|
|
|
});
|
2015-04-16 16:37:13 +02:00
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeBool = function () {
|
|
|
|
this._inputFormatter = f.formatInputBool;
|
|
|
|
this._outputFormatter = f.formatOutputBool;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeBool.prototype = new SolidityType({});
|
|
|
|
SolidityTypeBool.prototype.constructor = SolidityTypeBool;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeBool.prototype.isType = function (name) {
|
|
|
|
return name === 'bool';
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeBool.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
2015-07-27 22:52:55 +02:00
|
|
|
SolidityTypeBool.prototype.decode = function (bytes, offset, type) {
|
2015-07-27 22:38:33 +02:00
|
|
|
return new SolidityParam(bytes.substr(offset * 2, 64));
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeInt = function () {
|
|
|
|
this._inputFormatter = f.formatInputInt;
|
|
|
|
this._outputFormatter = f.formatOutputInt;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeInt.prototype = new SolidityType({});
|
|
|
|
SolidityTypeInt.prototype.constructor = SolidityTypeInt;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeInt.prototype.isType = function (name) {
|
|
|
|
return !!name.match(/^int([0-9]{1,3})?/);
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeInt.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeUInt = function () {
|
|
|
|
this._inputFormatter = f.formatInputInt;
|
|
|
|
this._outputFormatter = f.formatOutputUInt;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeUInt.prototype = new SolidityType({});
|
|
|
|
SolidityTypeUInt.prototype.constructor = SolidityTypeUInt;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeUInt.prototype.isType = function (name) {
|
|
|
|
return !!name.match(/^uint([0-9]{1,3})?/);
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeUInt.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeDynamicBytes = function () {
|
|
|
|
this._inputFormatter = f.formatInputDynamicBytes;
|
|
|
|
this._outputFormatter = f.formatOutputDynamicBytes;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeDynamicBytes.prototype = new SolidityType({});
|
|
|
|
SolidityTypeDynamicBytes.prototype.constructor = SolidityTypeDynamicBytes;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeDynamicBytes.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeDynamicBytes.prototype.isType = function (name) {
|
|
|
|
return name === 'bytes';
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeBytes = function () {
|
|
|
|
this._inputFormatter = f.formatInputBytes;
|
|
|
|
this._outputFormatter = f.formatOutputBytes;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeBytes.prototype = new SolidityType({});
|
|
|
|
SolidityTypeBytes.prototype.constructor = SolidityTypeBytes;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeBytes.prototype.isType = function (name) {
|
|
|
|
return !!name.match(/^bytes([0-9]{1,3})/);
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeBytes.prototype.staticPartLength = function (name) {
|
|
|
|
return parseInt(name.match(/^bytes([0-9]{1,3})/)[1]);
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeString = function () {
|
|
|
|
this._inputFormatter = f.formatInputString;
|
|
|
|
this._outputFormatter = f.formatOutputString;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeString.prototype = new SolidityType({});
|
|
|
|
SolidityTypeString.prototype.constructor = SolidityTypeString;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeString.prototype.isType = function (name) {
|
|
|
|
return name === 'string';
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeString.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeReal = function () {
|
|
|
|
this._inputFormatter = f.formatInputReal;
|
|
|
|
this._outputFormatter = f.formatOutputReal;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeReal.prototype = new SolidityType({});
|
|
|
|
SolidityTypeReal.prototype.constructor = SolidityTypeReal;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeReal.prototype.isType = function (name) {
|
|
|
|
return !!name.match(/^real([0-9]{1,3})?/);
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeReal.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
2015-07-27 15:10:10 +02:00
|
|
|
var SolidityTypeUReal = function () {
|
|
|
|
this._inputFormatter = f.formatInputReal;
|
|
|
|
this._outputFormatter = f.formatOutputUReal;
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeUReal.prototype = new SolidityType({});
|
|
|
|
SolidityTypeUReal.prototype.constructor = SolidityTypeUReal;
|
|
|
|
|
2015-07-27 22:38:33 +02:00
|
|
|
SolidityTypeUReal.prototype.isType = function (name) {
|
|
|
|
return !!name.match(/^ureal([0-9]{1,3})?/);
|
|
|
|
};
|
|
|
|
|
|
|
|
SolidityTypeUReal.prototype.staticPartLength = function (name) {
|
|
|
|
return 32;
|
|
|
|
};
|
|
|
|
|
2015-04-16 21:46:07 +02:00
|
|
|
var coder = new SolidityCoder([
|
2015-07-27 15:10:10 +02:00
|
|
|
new SolidityTypeAddress(),
|
|
|
|
new SolidityTypeBool(),
|
|
|
|
new SolidityTypeInt(),
|
|
|
|
new SolidityTypeUInt(),
|
|
|
|
new SolidityTypeDynamicBytes(),
|
|
|
|
new SolidityTypeBytes(),
|
|
|
|
new SolidityTypeString(),
|
|
|
|
new SolidityTypeReal(),
|
|
|
|
new SolidityTypeUReal()
|
2015-04-16 16:37:13 +02:00
|
|
|
]);
|
|
|
|
|
2015-04-16 21:46:07 +02:00
|
|
|
module.exports = coder;
|
2015-01-31 02:54:17 +01:00
|
|
|
|