web3.js/lib/solidity/coder.js

515 lines
16 KiB
JavaScript
Raw Normal View History

/*
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>
* @date 2015
*/
2015-04-16 16:37:13 +02:00
var BigNumber = require('bignumber.js');
var utils = require('../utils/utils');
var f = require('./formatters');
var SolidityParam = require('./param');
2015-04-20 11:20:05 +02:00
/**
* SolidityType prototype is used to encode/decode solidity params of certain type
*/
2015-04-16 21:16:26 +02:00
var SolidityType = function (config) {
2015-04-16 21:46:07 +02:00
this._inputFormatter = config.inputFormatter;
this._outputFormatter = config.outputFormatter;
2015-04-16 16:37:13 +02:00
};
2015-04-20 11:20:05 +02:00
/**
* Should be used to determine if this SolidityType do match given type
*
* @method isType
* @param {String} name
* @return {Bool} true if type match this SolidityType, otherwise false
*/
2015-04-16 16:37:13 +02:00
SolidityType.prototype.isType = function (name) {
2015-07-27 22:38:33 +02:00
throw "this method should be overrwritten!";
2015-04-16 16:37:13 +02:00
};
2015-07-28 00:14:41 +02:00
SolidityType.prototype.encode = function (value, name) {
if (this.isDynamicArray(name)) {
var length = value.length; // in int
var nestedName = this.nestedName(name);
var result = [];
2015-07-28 01:36:32 +02:00
result.push(f.formatInputInt(length).encode());
2015-07-28 00:14:41 +02:00
var self = this;
value.forEach(function (v) {
result.push(self.encode(v, nestedName));
});
return result;
} else if (this.isStaticArray(name)) {
var length = this.staticArrayLength(name); // in int
var nestedName = this.nestedName(name);
var result = [];
for (var i = 0; i < length; i++) {
result.push(this.encode(value[i], nestedName));
}
return result;
}
return this._inputFormatter(value, name).encode();
};
2015-04-20 11:20:05 +02:00
/**
2015-07-27 22:52:55 +02:00
* Should be used to decode params from bytes
2015-04-20 11:20:05 +02:00
*
2015-07-27 22:52:55 +02:00
* @method decode
* @param {String} bytes
* @param {Number} offset in bytes
* @param {String} name type name
* @returns {SolidityParam} param
2015-04-20 11:20:05 +02:00
*/
2015-07-27 22:52:55 +02:00
SolidityType.prototype.decode = function (bytes, offset, name) {
if (this.isDynamicArray(name)) {
var arrayOffset = parseInt('0x' + bytes.substr(offset * 2, 64)); // in bytes
var length = parseInt('0x' + bytes.substr(arrayOffset * 2, 64)); // in int
var arrayStart = arrayOffset + 32; // array starts after length; // in bytes
var nestedName = this.nestedName(name);
var nestedStaticPartLength = this.staticPartLength(nestedName); // in bytes
2015-04-16 21:16:26 +02:00
var result = [];
2015-07-27 22:52:55 +02:00
for (var i = 0; i < length * nestedStaticPartLength; i += nestedStaticPartLength) {
result.push(this.decode(bytes, arrayStart + i, nestedName));
}
2015-07-27 22:52:55 +02:00
2015-04-16 21:16:26 +02:00
return result;
2015-07-27 22:52:55 +02:00
} else if (this.isStaticArray(name)) {
var length = this.staticArrayLength(name); // in int
var arrayStart = offset; // in bytes
2015-04-16 16:37:13 +02:00
2015-07-27 22:52:55 +02:00
var nestedName = this.nestedName(name);
var nestedStaticPartLength = this.staticPartLength(nestedName); // in bytes
var result = [];
for (var i = 0; i < length * nestedStaticPartLength; i += nestedStaticPartLength) {
result.push(this.decode(bytes, arrayStart + i, nestedName));
}
return result;
2015-04-16 21:16:26 +02:00
}
2015-07-27 22:52:55 +02:00
var length = this.staticPartLength(name);
return this._outputFormatter(new SolidityParam(bytes.substr(offset * 2, length * 2)));
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-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 SolidityTypeAddress = function () {
this._inputFormatter = f.formatInputInt;
this._outputFormatter = f.formatOutputAddress;
};
SolidityTypeAddress.prototype = new SolidityType({});
SolidityTypeAddress.prototype.constructor = SolidityTypeAddress;
2015-07-27 22:38:33 +02:00
SolidityTypeAddress.prototype.isType = function (name) {
return !!name.match(/address(\[([0-9]*)\])?/);
};
SolidityTypeAddress.prototype.staticPartLength = function (name) {
return 32 * this.staticArrayLength(name);
};
SolidityTypeAddress.prototype.isDynamicArray = function (name) {
var matches = name.match(/address(\[([0-9]*)\])?/);
// is array && doesn't have length specified
return !!matches[1] && !matches[2];
};
SolidityTypeAddress.prototype.isStaticArray = function (name) {
var matches = name.match(/address(\[([0-9]*)\])?/);
// is array && have length specified
return !!matches[1] && !!matches[2];
};
SolidityTypeAddress.prototype.staticArrayLength = function (name) {
return name.match(/address(\[([0-9]*)\])?/)[2] || 1;
};
SolidityTypeAddress.prototype.nestedName = function (name) {
// removes first [] in name
return name.replace(/\[([0-9])*\]/, '');
};
SolidityTypeAddress.prototype.formatOutput = function (param, unused, name) {
if (this.isStaticArray(name)) {
var staticPart = param.staticPart();
var result = [];
for (var i = 0; i < staticPart.length; i += 64) {
result.push(this._outputFormatter(new SolidityParam(staticPart.substr(0, i + 64))));
}
return result;
} else if (this.isDynamicArray(name)) {
var dynamicPart = param.dynamicPart();
var result = [];
// first position of dynamic part is the length of the array
var length = new BigNumber(param.dynamicPart().slice(0, 64), 16);
for (var i = 0; i < length * 64; i += 64) {
result.push(this._outputFormatter(new SolidityParam(dynamicPart.substr(i + 64, 64))));
}
return result;
}
return this._outputFormatter(param);
};
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;