web3.js/lib/solidity/coder.js

290 lines
8.2 KiB
JavaScript

/*
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/>.
*/
/**
* @file coder.js
* @author Marek Kotewicz <[email protected]>
* @date 2015
*/
var BigNumber = require('bignumber.js');
var utils = require('../utils/utils');
var f = require('./formatters');
var SolidityParam = require('./param');
/**
* Should be used to check if a type is an array type
*
* @method isArrayType
* @param {String} type
* @return {Bool} true is the type is an array, otherwise false
*/
var isArrayType = function (type) {
return type.slice(-2) === '[]';
};
/**
* SolidityType prototype is used to encode/decode solidity params of certain type
*/
var SolidityType = function (config) {
this._name = config.name;
this._match = config.match;
this._mode = config.mode;
this._inputFormatter = config.inputFormatter;
this._outputFormatter = config.outputFormatter;
};
/**
* 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
*/
SolidityType.prototype.isType = function (name) {
if (this._match === 'strict') {
return this._name === name || (name.indexOf(this._name) === 0 && name.slice(this._name.length) === '[]');
} else if (this._match === 'prefix') {
// TODO better type detection!
return name.indexOf(this._name) === 0;
}
};
/**
* Should be used to transform plain param to SolidityParam object
*
* @method formatInput
* @param {Object} param - plain object, or an array of objects
* @param {Bool} arrayType - true if a param should be encoded as an array
* @return {SolidityParam} encoded param wrapped in SolidityParam object
*/
SolidityType.prototype.formatInput = function (param, arrayType) {
if (utils.isArray(param) && arrayType) { // TODO: should fail if this two are not the same
var self = this;
return param.map(function (p) {
return self._inputFormatter(p);
}).reduce(function (acc, current) {
return acc.combine(current);
}, f.formatInputInt(param.length)).withOffset(32);
}
return this._inputFormatter(param);
};
/**
* Should be used to transoform SolidityParam to plain param
*
* @method formatOutput
* @param {SolidityParam} byteArray
* @param {Bool} arrayType - true if a param should be decoded as an array
* @return {Object} plain decoded param
*/
SolidityType.prototype.formatOutput = function (param, arrayType) {
if (arrayType) {
// let's assume, that we solidity will never return long arrays :P
var result = [];
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(param.dynamicPart().substr(i + 64, 64))));
}
return result;
}
return this._outputFormatter(param);
};
/**
* Should be used to slice single param from bytes
*
* @method sliceParam
* @param {String} bytes
* @param {Number} index of param to slice
* @param {String} type
* @returns {SolidityParam} param
*/
SolidityType.prototype.sliceParam = function (bytes, index, type) {
if (this._mode === 'bytes') {
return SolidityParam.decodeBytes(bytes, index);
} else if (isArrayType(type)) {
return SolidityParam.decodeArray(bytes, index);
}
return SolidityParam.decodeParam(bytes, index);
};
/**
* 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 transform plain param of given type to SolidityParam
*
* @method _formatInput
* @param {String} type of param
* @param {Object} plain param
* @return {SolidityParam}
*/
SolidityCoder.prototype._formatInput = function (type, param) {
return this._requireType(type).formatInput(param, isArrayType(type));
};
/**
* 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._formatInput(type, param).encode();
};
/**
* 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 self = this;
var solidityParams = types.map(function (type, index) {
return self._formatInput(type, params[index]);
});
return SolidityParam.encodeList(solidityParams);
};
/**
* 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 self = this;
return types.map(function (type, index) {
var solidityType = self._requireType(type);
var p = solidityType.sliceParam(bytes, index, type);
return solidityType.formatOutput(p, isArrayType(type));
});
};
var coder = new SolidityCoder([
new SolidityType({
name: 'address',
match: 'strict',
mode: 'value',
inputFormatter: f.formatInputInt,
outputFormatter: f.formatOutputAddress
}),
new SolidityType({
name: 'bool',
match: 'strict',
mode: 'value',
inputFormatter: f.formatInputBool,
outputFormatter: f.formatOutputBool
}),
new SolidityType({
name: 'int',
match: 'prefix',
mode: 'value',
inputFormatter: f.formatInputInt,
outputFormatter: f.formatOutputInt,
}),
new SolidityType({
name: 'uint',
match: 'prefix',
mode: 'value',
inputFormatter: f.formatInputInt,
outputFormatter: f.formatOutputUInt
}),
new SolidityType({
name: 'bytes',
match: 'strict',
mode: 'bytes',
inputFormatter: f.formatInputDynamicBytes,
outputFormatter: f.formatOutputDynamicBytes
}),
new SolidityType({
name: 'bytes',
match: 'prefix',
mode: 'value',
inputFormatter: f.formatInputBytes,
outputFormatter: f.formatOutputBytes
}),
new SolidityType({
name: 'string',
match: 'strict',
mode: 'bytes',
inputFormatter: f.formatInputString,
outputFormatter: f.formatOutputString
}),
new SolidityType({
name: 'real',
match: 'prefix',
mode: 'value',
inputFormatter: f.formatInputReal,
outputFormatter: f.formatOutputReal
}),
new SolidityType({
name: 'ureal',
match: 'prefix',
mode: 'value',
inputFormatter: f.formatInputReal,
outputFormatter: f.formatOutputUReal
})
]);
module.exports = coder;