go-ethereum/lib/abi.js

309 lines
10 KiB
JavaScript
Raw Normal View History

2014-11-14 14:11:47 +02: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/>.
*/
/** @file abi.js
* @authors:
* Marek Kotewicz <marek@ethdev.com>
2014-12-22 02:13:49 +02:00
* Gav Wood <g@ethdev.com>
2014-11-14 14:11:47 +02:00
* @date 2014
*/
2014-11-12 19:59:29 +02:00
2015-01-06 19:29:38 +02:00
// TODO: is these line is supposed to be here?
if (process.env.NODE_ENV !== 'build') {
var BigNumber = require('bignumber.js'); // jshint ignore:line
2015-01-06 19:29:38 +02:00
}
var web3 = require('./web3'); // jshint ignore:line
2015-01-16 11:47:43 +02:00
2014-12-22 02:13:49 +02:00
// TODO: make these be actually accurate instead of falling back onto JS's doubles.
var hexToDec = function (hex) {
return parseInt(hex, 16).toString();
};
var decToHex = function (dec) {
return parseInt(dec).toString(16);
};
2015-01-14 15:19:54 +02:00
/// Finds first index of array element matching pattern
/// @param array
/// @param callback pattern
/// @returns index of element
2014-11-12 19:59:29 +02:00
var findIndex = function (array, callback) {
var end = false;
var i = 0;
for (; i < array.length && !end; i++) {
end = callback(array[i]);
}
return end ? i - 1 : -1;
};
2015-01-14 15:19:54 +02:00
/// @returns a function that is used as a pattern for 'findIndex'
2014-11-13 05:21:51 +02:00
var findMethodIndex = function (json, methodName) {
return findIndex(json, function (method) {
return method.name === methodName;
});
};
2015-01-14 15:19:54 +02:00
/// @param string string to be padded
/// @param number of characters that result string should have
2015-01-15 16:51:25 +02:00
/// @param sign, by default 0
2015-01-14 15:19:54 +02:00
/// @returns right aligned string
2015-01-15 16:51:25 +02:00
var padLeft = function (string, chars, sign) {
return new Array(chars - string.length + 1).join(sign ? sign : "0") + string;
2014-11-12 19:59:29 +02:00
};
2015-01-14 15:19:54 +02:00
/// @param expected type prefix (string)
/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false
var prefixedType = function (prefix) {
return function (type) {
return type.indexOf(prefix) === 0;
2014-11-12 19:59:29 +02:00
};
2015-01-14 15:19:54 +02:00
};
2014-11-12 19:59:29 +02:00
2015-01-14 15:19:54 +02:00
/// @param expected type name (string)
/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false
var namedType = function (name) {
return function (type) {
return name === type;
2014-11-12 19:59:29 +02:00
};
2015-01-14 15:19:54 +02:00
};
/// Setups input formatters for solidity types
/// @returns an array of input formatters
var setupInputTypes = function () {
2015-01-14 14:53:40 +02:00
2015-01-14 21:36:26 +02:00
/// Formats input value to byte representation of int
2015-01-16 11:47:43 +02:00
/// If value is negative, return it's two's complement
2015-01-14 21:36:26 +02:00
/// @returns right-aligned byte representation of int
2015-01-14 14:53:40 +02:00
var formatInt = function (value) {
var padding = 32 * 2;
2015-01-16 11:47:43 +02:00
if (value instanceof BigNumber) {
if (value.lessThan(0))
value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1).toString(16);
else
value = value.toString(16);
}
else if (typeof value === 'number') {
if (value < 0)
value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1).toString(16);
else
value = new BigNumber(value).toString(16);
2015-01-15 16:51:25 +02:00
}
2015-01-14 14:53:40 +02:00
else if (value.indexOf('0x') === 0)
value = value.substr(2);
else if (typeof value === 'string')
2015-01-16 11:47:43 +02:00
value = new BigNumber(value).toString(16);
2015-01-14 14:53:40 +02:00
else
value = (+value).toString(16);
return padLeft(value, padding);
};
2015-01-14 21:36:26 +02:00
/// Formats input value to byte representation of string
/// @returns left-algined byte representation of string
2015-01-14 14:53:40 +02:00
var formatString = function (value) {
return web3.fromAscii(value, 32).substr(2);
};
2014-11-12 19:59:29 +02:00
2015-01-14 21:36:26 +02:00
/// Formats input value to byte representation of bool
/// @returns right-aligned byte representation bool
2014-11-13 05:21:51 +02:00
var formatBool = function (value) {
2015-01-14 14:53:40 +02:00
return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0');
2014-11-13 05:21:51 +02:00
};
2014-11-12 19:59:29 +02:00
return [
2015-01-14 14:53:40 +02:00
{ type: prefixedType('uint'), format: formatInt },
{ type: prefixedType('int'), format: formatInt },
{ type: prefixedType('hash'), format: formatInt },
{ type: prefixedType('string'), format: formatString },
{ type: prefixedType('real'), format: formatInt },
{ type: prefixedType('ureal'), format: formatInt },
{ type: namedType('address'), format: formatInt },
2015-01-14 14:53:40 +02:00
{ type: namedType('bool'), format: formatBool }
2014-11-12 19:59:29 +02:00
];
};
2014-11-13 05:21:51 +02:00
var inputTypes = setupInputTypes();
2014-11-12 19:59:29 +02:00
2015-01-14 15:19:54 +02:00
/// Formats input params to bytes
/// @param contract json abi
/// @param name of the method that we want to use
/// @param array of params that will be formatted to bytes
/// @returns bytes representation of input params
2014-11-13 05:21:51 +02:00
var toAbiInput = function (json, methodName, params) {
2014-11-12 19:59:29 +02:00
var bytes = "";
2014-11-13 05:21:51 +02:00
var index = findMethodIndex(json, methodName);
2014-12-22 02:13:49 +02:00
2014-11-12 19:59:29 +02:00
if (index === -1) {
return;
}
var method = json[index];
2015-01-14 14:53:40 +02:00
var padding = 32 * 2;
2014-12-22 02:13:49 +02:00
2014-11-12 19:59:29 +02:00
for (var i = 0; i < method.inputs.length; i++) {
2015-01-14 14:53:40 +02:00
var typeMatch = false;
for (var j = 0; j < inputTypes.length && !typeMatch; j++) {
typeMatch = inputTypes[j].type(method.inputs[i].type, params[i]);
2014-11-12 19:59:29 +02:00
}
2015-01-14 14:53:40 +02:00
if (!typeMatch) {
console.error('input parser does not support type: ' + method.inputs[i].type);
2014-11-12 19:59:29 +02:00
}
2015-01-14 14:53:40 +02:00
var formatter = inputTypes[j - 1].format;
bytes += (formatter ? formatter(params[i]) : params[i]);
2014-11-12 19:59:29 +02:00
}
return bytes;
};
2015-01-14 14:53:40 +02:00
/// Setups output formaters for solidity types
/// @returns an array of output formatters
2014-11-13 05:21:51 +02:00
var setupOutputTypes = function () {
2015-01-06 19:29:38 +02:00
2015-01-14 21:36:26 +02:00
/// Formats input right-aligned input bytes to int
/// @returns right-aligned input bytes formatted to int
2014-11-13 05:21:51 +02:00
var formatInt = function (value) {
2015-01-16 12:58:26 +02:00
// check if it's negative number
// it it is, return two's complement
if (value.substr(0, 1).toLowerCase() === 'f') {
return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1);
}
return new BigNumber(value, 16);
};
var formatUInt = function (value) {
return new BigNumber(value, 16);
2014-12-22 02:13:49 +02:00
};
2015-01-14 21:36:26 +02:00
/// @returns right-aligned input bytes formatted to hex
2014-12-22 02:13:49 +02:00
var formatHash = function (value) {
return "0x" + value;
2014-11-13 05:21:51 +02:00
};
2015-01-14 21:36:26 +02:00
/// @returns right-aligned input bytes formatted to bool
2014-11-13 05:21:51 +02:00
var formatBool = function (value) {
2015-01-14 15:06:29 +02:00
return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false;
2014-11-13 05:21:51 +02:00
};
2015-01-14 21:36:26 +02:00
/// @returns left-aligned input bytes formatted to ascii string
2015-01-06 19:29:38 +02:00
var formatString = function (value) {
return web3.toAscii(value);
};
2015-01-14 21:36:26 +02:00
/// @returns right-aligned input bytes formatted to address
var formatAddress = function (value) {
return "0x" + value.slice(value.length - 40, value.length);
};
2014-11-13 05:21:51 +02:00
return [
2015-01-16 12:58:26 +02:00
{ type: prefixedType('uint'), format: formatUInt },
2015-01-14 14:53:40 +02:00
{ type: prefixedType('int'), format: formatInt },
{ type: prefixedType('hash'), format: formatHash },
{ type: prefixedType('string'), format: formatString },
{ type: prefixedType('real'), format: formatInt },
{ type: prefixedType('ureal'), format: formatInt },
{ type: namedType('address'), format: formatAddress },
2015-01-14 14:53:40 +02:00
{ type: namedType('bool'), format: formatBool }
2014-11-13 05:21:51 +02:00
];
};
var outputTypes = setupOutputTypes();
2015-01-14 15:19:54 +02:00
/// Formats output bytes back to param list
/// @param contract json abi
/// @param name of the method that we want to use
/// @param bytes representtion of output
/// @returns array of output params
2014-11-13 05:21:51 +02:00
var fromAbiOutput = function (json, methodName, output) {
var index = findMethodIndex(json, methodName);
if (index === -1) {
return;
}
2014-12-22 02:13:49 +02:00
2014-11-13 05:21:51 +02:00
output = output.slice(2);
var result = [];
var method = json[index];
2015-01-14 14:53:40 +02:00
var padding = 32 * 2;
2014-11-13 05:21:51 +02:00
for (var i = 0; i < method.outputs.length; i++) {
2015-01-14 14:53:40 +02:00
var typeMatch = false;
for (var j = 0; j < outputTypes.length && !typeMatch; j++) {
typeMatch = outputTypes[j].type(method.outputs[i].type);
2014-11-13 05:21:51 +02:00
}
2015-01-14 14:53:40 +02:00
if (!typeMatch) {
2014-11-13 05:21:51 +02:00
// not found output parsing
2015-01-14 14:53:40 +02:00
console.error('output parser does not support type: ' + method.outputs[i].type);
2014-11-13 05:21:51 +02:00
continue;
}
var res = output.slice(0, padding);
var formatter = outputTypes[j - 1].format;
2014-12-22 02:13:49 +02:00
result.push(formatter ? formatter(res) : ("0x" + res));
2014-11-13 05:21:51 +02:00
output = output.slice(padding);
}
return result;
};
2015-01-14 14:53:40 +02:00
/// @param json abi for contract
/// @returns input parser object for given json abi
2014-11-14 14:11:47 +02:00
var inputParser = function (json) {
var parser = {};
2014-11-13 13:24:34 +02:00
json.forEach(function (method) {
2014-11-14 14:11:47 +02:00
parser[method.name] = function () {
2014-11-13 13:24:34 +02:00
var params = Array.prototype.slice.call(arguments);
return toAbiInput(json, method.name, params);
};
});
2014-11-14 14:11:47 +02:00
return parser;
2014-11-12 19:59:29 +02:00
};
2015-01-14 14:53:40 +02:00
/// @param json abi for contract
/// @returns output parser for given json abi
2014-11-14 14:11:47 +02:00
var outputParser = function (json) {
var parser = {};
json.forEach(function (method) {
parser[method.name] = function (output) {
return fromAbiOutput(json, method.name, output);
};
});
return parser;
};
2015-01-14 14:53:40 +02:00
/// @param json abi for contract
/// @param method name for which we want to get method signature
/// @returns (promise) contract method signature for method with given name
2015-01-09 13:55:04 +02:00
var methodSignature = function (json, name) {
var method = json[findMethodIndex(json, name)];
var result = name + '(';
var inputTypes = method.inputs.map(function (inp) {
return inp.type;
});
result += inputTypes.join(',');
result += ')';
2015-01-09 17:38:26 +02:00
return web3.sha3(web3.fromAscii(result));
2015-01-09 13:55:04 +02:00
};
2014-11-14 14:11:47 +02:00
module.exports = {
inputParser: inputParser,
2015-01-09 13:55:04 +02:00
outputParser: outputParser,
methodSignature: methodSignature
2014-11-14 14:11:47 +02:00
};