web3.js/lib/solidity/coder.js

260 lines
8.2 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-07-28 03:44:19 +02:00
var f = require('./formatters');
2015-07-28 03:44:19 +02:00
var SolidityTypeAddress = require('./address');
2015-07-28 09:01:05 +02:00
var SolidityTypeBool = require('./bool');
2015-07-28 10:13:59 +02:00
var SolidityTypeInt = require('./int');
var SolidityTypeUInt = require('./uint');
var SolidityTypeDynamicBytes = require('./dynamicbytes');
2015-07-28 15:04:33 +02:00
var SolidityTypeString = require('./string');
2015-07-28 15:56:18 +02:00
var SolidityTypeReal = require('./real');
var SolidityTypeUReal = require('./ureal');
2015-07-28 16:47:56 +02:00
var SolidityTypeBytes = require('./bytes');
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);
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]);
});
var dynamicOffset = solidityTypes.reduce(function (acc, solidityType, index) {
2015-07-28 00:32:46 +02:00
return acc + solidityType.staticPartLength(types[index]);
}, 0);
var result = this.encodeMultiWithOffset(types, solidityTypes, encodeds, dynamicOffset);
return result;
};
2015-07-28 00:32:46 +02:00
SolidityCoder.prototype.encodeMultiWithOffset = function (types, solidityTypes, encodeds, dynamicOffset) {
var result = "";
2015-07-29 18:02:34 +02:00
var self = this;
2015-07-28 15:46:38 +02:00
var isDynamic = function (i) {
return solidityTypes[i].isDynamicArray(types[i]) || solidityTypes[i].isDynamicType(types[i]);
2015-07-29 18:02:34 +02:00
};
2015-07-28 15:46:38 +02:00
2015-07-29 18:02:34 +02:00
types.forEach(function (type, i) {
2015-07-28 15:46:38 +02:00
if (isDynamic(i)) {
result += f.formatInputInt(dynamicOffset).encode();
2015-07-29 18:02:34 +02:00
var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset);
dynamicOffset += e.length / 2;
2015-07-28 15:46:38 +02:00
} else {
2015-07-29 18:02:34 +02:00
// don't add length to dynamicOffset. it's already counted
result += self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset);
2015-07-28 01:43:28 +02:00
}
2015-07-28 15:46:38 +02:00
// TODO: figure out nested arrays
2015-07-29 18:02:34 +02:00
});
2015-07-28 15:46:38 +02:00
2015-07-29 18:02:34 +02:00
types.forEach(function (type, i) {
2015-07-28 15:46:38 +02:00
if (isDynamic(i)) {
2015-07-29 18:02:34 +02:00
var e = self.encodeWithOffset(types[i], solidityTypes[i], encodeds[i], dynamicOffset);
2015-07-28 15:46:38 +02:00
dynamicOffset += e.length / 2;
result += e;
}
2015-07-29 18:02:34 +02:00
});
2015-07-28 00:32:46 +02:00
return result;
2015-04-16 21:16:26 +02:00
};
2015-08-03 15:14:54 +02:00
// TODO: refactor whole encoding!
2015-07-28 01:36:32 +02:00
SolidityCoder.prototype.encodeWithOffset = function (type, solidityType, encoded, offset) {
2015-08-03 15:14:54 +02:00
var self = this;
2015-07-28 01:36:32 +02:00
if (solidityType.isDynamicArray(type)) {
2015-08-03 15:14:54 +02:00
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;
})();
2015-07-28 01:36:32 +02:00
} else if (solidityType.isStaticArray(type)) {
2015-08-03 15:14:54 +02:00
return (function () {
var nestedName = solidityType.nestedName(type);
var nestedStaticPartLength = solidityType.staticPartLength(nestedName);
var result = "";
2015-08-07 11:13:26 +02:00
if (solidityType.isDynamicArray(nestedName)) {
(function () {
var previousLength = 0; // in int
2015-08-03 15:14:54 +02:00
for (var i = 0; i < encoded.length; i++) {
// calculate length of previous item
2015-08-07 11:13:26 +02:00
previousLength += +(encoded[i - 1] || [])[0] || 0;
2015-08-03 15:14:54 +02:00
result += f.formatInputInt(offset + i * nestedStaticPartLength + previousLength * 32).encode();
}
2015-08-07 11:13:26 +02:00
})();
}
2015-08-03 15:14:54 +02:00
(function () {
for (var i = 0; i < encoded.length; i++) {
var additionalOffset = result / 2;
result += self.encodeWithOffset(nestedName, solidityType, encoded[i], offset + additionalOffset);
}
})();
return result;
})();
2015-07-28 15:46:38 +02:00
}
2015-07-28 01:36:32 +02:00
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) {
2015-07-28 15:04:33 +02:00
var lengths = solidityTypes.map(function (solidityType, index) {
2015-07-27 22:38:33 +02:00
return solidityType.staticPartLength(types[index]);
// get length
2015-07-29 18:02:34 +02:00
});
2015-07-28 15:04:33 +02:00
for (var i = 0; i < lengths.length; i++) {
2015-07-27 22:38:33 +02:00
// sum with length of previous element
2015-07-28 15:04:33 +02:00
var previous = (lengths[i - 1] || 0);
lengths[i] += previous;
}
return lengths.map(function (length, index) {
2015-07-27 22:38:33 +02:00
// 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
};
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-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;