plugeth/lib/abi.js

333 lines
11 KiB
JavaScript
Raw Normal View History

2014-11-14 12:11:47 +00: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 00:13:49 +00:00
* Gav Wood <g@ethdev.com>
2014-11-14 12:11:47 +00:00
* @date 2014
*/
2014-11-12 17:59:29 +00:00
2015-01-06 17:29:38 +00:00
if (process.env.NODE_ENV !== 'build') {
var BigNumber = require('bignumber.js'); // jshint ignore:line
2015-01-06 17:29:38 +00:00
}
2015-01-31 00:52:36 +00:00
var web3 = require('./web3');
var f = require('./formatters');
2015-01-16 09:47:43 +00:00
BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_DOWN });
var ETH_PADDING = 32;
/// method signature length in bytes
var ETH_METHOD_SIGNATURE_LENGTH = 4;
2015-01-14 13:19:54 +00:00
/// Finds first index of array element matching pattern
/// @param array
/// @param callback pattern
/// @returns index of element
2014-11-12 17:59:29 +00: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 13:19:54 +00:00
/// @returns a function that is used as a pattern for 'findIndex'
2014-11-13 03:21:51 +00:00
var findMethodIndex = function (json, methodName) {
return findIndex(json, function (method) {
return method.name === methodName;
});
};
2015-01-27 13:05:06 +00:00
/// @returns method with given method name
var getMethodWithName = function (json, methodName) {
var index = findMethodIndex(json, methodName);
if (index === -1) {
console.error('method ' + methodName + ' not found in the abi');
return undefined;
}
return json[index];
};
2015-01-28 13:20:36 +00:00
/// Filters all function from input abi
/// @returns abi array with filtered objects of type 'function'
var filterFunctions = function (json) {
return json.filter(function (current) {
return current.type === 'function';
});
};
2015-01-28 13:39:10 +00:00
/// Filters all events form input abi
/// @returns abi array with filtered objects of type 'event'
var filterEvents = function (json) {
return json.filter(function (current) {
return current.type === 'event';
});
};
2015-01-14 13:19:54 +00:00
/// @param string string to be padded
/// @param number of characters that result string should have
2015-01-15 14:51:25 +00:00
/// @param sign, by default 0
2015-01-14 13:19:54 +00:00
/// @returns right aligned string
2015-01-31 00:52:36 +00:00
/// TODO: remove, it was moved to formatters.js
2015-01-15 14:51:25 +00:00
var padLeft = function (string, chars, sign) {
return new Array(chars - string.length + 1).join(sign ? sign : "0") + string;
2014-11-12 17:59:29 +00:00
};
2015-01-14 13:19:54 +00: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 17:59:29 +00:00
};
2015-01-14 13:19:54 +00:00
};
2014-11-12 17:59:29 +00:00
2015-01-14 13:19:54 +00: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 17:59:29 +00:00
};
2015-01-14 13:19:54 +00:00
};
2015-01-31 00:52:36 +00:00
/// This method should be called if we want to check if givent type is an array type
/// @returns true if it is, otherwise false
2015-01-17 01:14:40 +00:00
var arrayType = function (type) {
return type.slice(-2) === '[]';
};
var dynamicTypeBytes = function (type, value) {
// TODO: decide what to do with array of strings
if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.
2015-01-31 00:52:36 +00:00
return f.formatInputInt(value.length);
2015-01-17 01:14:40 +00:00
return "";
};
2015-01-14 13:19:54 +00:00
/// Setups input formatters for solidity types
/// @returns an array of input formatters
var setupInputTypes = function () {
2015-01-14 12:53:40 +00:00
2014-11-12 17:59:29 +00:00
return [
2015-01-31 00:52:36 +00:00
{ type: prefixedType('uint'), format: f.formatInputInt },
{ type: prefixedType('int'), format: f.formatInputInt },
{ type: prefixedType('hash'), format: f.formatInputInt },
{ type: prefixedType('string'), format: f.formatInputString },
{ type: prefixedType('real'), format: f.formatInputReal },
{ type: prefixedType('ureal'), format: f.formatInputReal },
{ type: namedType('address'), format: f.formatInputInt },
{ type: namedType('bool'), format: f.formatInputBool }
2014-11-12 17:59:29 +00:00
];
};
2014-11-13 03:21:51 +00:00
var inputTypes = setupInputTypes();
2014-11-12 17:59:29 +00:00
2015-01-14 13:19:54 +00: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 03:21:51 +00:00
var toAbiInput = function (json, methodName, params) {
2014-11-12 17:59:29 +00:00
var bytes = "";
2015-01-27 13:05:06 +00:00
var method = getMethodWithName(json, methodName);
var padding = ETH_PADDING * 2;
2014-12-22 00:13:49 +00:00
2015-01-17 01:14:40 +00:00
/// first we iterate in search for dynamic
method.inputs.forEach(function (input, index) {
bytes += dynamicTypeBytes(input.type, params[index]);
});
method.inputs.forEach(function (input, i) {
2015-01-14 12:53:40 +00: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 17:59:29 +00:00
}
2015-01-14 12:53:40 +00:00
if (!typeMatch) {
console.error('input parser does not support type: ' + method.inputs[i].type);
2014-11-12 17:59:29 +00:00
}
2015-01-14 12:53:40 +00:00
var formatter = inputTypes[j - 1].format;
2015-01-17 01:14:40 +00:00
var toAppend = "";
if (arrayType(method.inputs[i].type))
toAppend = params[i].reduce(function (acc, curr) {
return acc + formatter(curr);
}, "");
else
toAppend = formatter(params[i]);
bytes += toAppend;
});
2014-11-12 17:59:29 +00:00
return bytes;
};
2015-01-17 12:39:19 +00:00
var dynamicBytesLength = function (type) {
if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.
2015-01-17 12:39:19 +00:00
return ETH_PADDING * 2;
return 0;
};
/// Setups output formaters for solidity types
/// @returns an array of output formatters
var setupOutputTypes = function () {
2014-11-13 03:21:51 +00:00
return [
2015-01-31 00:52:36 +00:00
{ type: prefixedType('uint'), format: f.formatOutputUInt },
{ type: prefixedType('int'), format: f.formatOutputInt },
{ type: prefixedType('hash'), format: f.formatOutputHash },
{ type: prefixedType('string'), format: f.formatOutputString },
{ type: prefixedType('real'), format: f.formatOutputReal },
{ type: prefixedType('ureal'), format: f.formatOutputUReal },
{ type: namedType('address'), format: f.formatOutputAddress },
{ type: namedType('bool'), format: f.formatOutputBool }
2014-11-13 03:21:51 +00:00
];
};
var outputTypes = setupOutputTypes();
2015-01-14 13:19:54 +00: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 03:21:51 +00:00
var fromAbiOutput = function (json, methodName, output) {
2015-01-27 13:05:06 +00:00
2014-11-13 03:21:51 +00:00
output = output.slice(2);
var result = [];
2015-01-27 13:05:06 +00:00
var method = getMethodWithName(json, methodName);
var padding = ETH_PADDING * 2;
2015-01-17 12:39:19 +00:00
var dynamicPartLength = method.outputs.reduce(function (acc, curr) {
return acc + dynamicBytesLength(curr.type);
}, 0);
var dynamicPart = output.slice(0, dynamicPartLength);
output = output.slice(dynamicPartLength);
method.outputs.forEach(function (out, i) {
2015-01-14 12:53:40 +00:00
var typeMatch = false;
for (var j = 0; j < outputTypes.length && !typeMatch; j++) {
typeMatch = outputTypes[j].type(method.outputs[i].type);
2014-11-13 03:21:51 +00:00
}
2015-01-14 12:53:40 +00:00
if (!typeMatch) {
console.error('output parser does not support type: ' + method.outputs[i].type);
2014-11-13 03:21:51 +00:00
}
2015-01-17 12:39:19 +00:00
2014-11-13 03:21:51 +00:00
var formatter = outputTypes[j - 1].format;
2015-01-17 12:39:19 +00:00
if (arrayType(method.outputs[i].type)) {
2015-01-31 00:52:36 +00:00
var size = f.formatOutputUInt(dynamicPart.slice(0, padding));
2015-01-17 12:39:19 +00:00
dynamicPart = dynamicPart.slice(padding);
var array = [];
for (var k = 0; k < size; k++) {
array.push(formatter(output.slice(0, padding)));
output = output.slice(padding);
}
result.push(array);
}
else if (prefixedType('string')(method.outputs[i].type)) {
dynamicPart = dynamicPart.slice(padding);
result.push(formatter(output.slice(0, padding)));
output = output.slice(padding);
} else {
result.push(formatter(output.slice(0, padding)));
output = output.slice(padding);
}
});
2014-11-13 03:21:51 +00:00
return result;
};
2015-01-20 14:06:05 +00:00
/// @returns display name for method eg. multiply(uint256) -> multiply
var methodDisplayName = function (method) {
var length = method.indexOf('(');
return length !== -1 ? method.substr(0, length) : method;
};
/// @returns overloaded part of method's name
var methodTypeName = function (method) {
/// TODO: make it not vulnerable
var length = method.indexOf('(');
return length !== -1 ? method.substr(length + 1, method.length - 1 - (length + 1)) : "";
};
2015-01-14 12:53:40 +00:00
/// @param json abi for contract
/// @returns input parser object for given json abi
2014-11-14 12:11:47 +00:00
var inputParser = function (json) {
var parser = {};
2015-01-28 13:20:36 +00:00
filterFunctions(json).forEach(function (method) {
2015-01-20 14:06:05 +00:00
var displayName = methodDisplayName(method.name);
var typeName = methodTypeName(method.name);
var impl = function () {
2014-11-13 11:24:34 +00:00
var params = Array.prototype.slice.call(arguments);
return toAbiInput(json, method.name, params);
};
2015-01-20 14:06:05 +00:00
if (parser[displayName] === undefined) {
parser[displayName] = impl;
}
parser[displayName][typeName] = impl;
2014-11-13 11:24:34 +00:00
});
2014-11-14 12:11:47 +00:00
return parser;
2014-11-12 17:59:29 +00:00
};
2015-01-14 12:53:40 +00:00
/// @param json abi for contract
/// @returns output parser for given json abi
2014-11-14 12:11:47 +00:00
var outputParser = function (json) {
var parser = {};
2015-01-28 13:20:36 +00:00
filterFunctions(json).forEach(function (method) {
2015-01-20 14:06:05 +00:00
var displayName = methodDisplayName(method.name);
var typeName = methodTypeName(method.name);
var impl = function (output) {
2014-11-14 12:11:47 +00:00
return fromAbiOutput(json, method.name, output);
};
2015-01-20 14:06:05 +00:00
if (parser[displayName] === undefined) {
parser[displayName] = impl;
}
parser[displayName][typeName] = impl;
2014-11-14 12:11:47 +00:00
});
return parser;
};
2015-01-14 12:53:40 +00:00
/// @param method name for which we want to get method signature
/// @returns (promise) contract method signature for method with given name
var methodSignature = function (name) {
return web3.sha3(web3.fromAscii(name)).slice(0, 2 + ETH_METHOD_SIGNATURE_LENGTH * 2);
2015-01-09 11:55:04 +00:00
};
2014-11-14 12:11:47 +00:00
module.exports = {
inputParser: inputParser,
2015-01-09 11:55:04 +00:00
outputParser: outputParser,
2015-01-20 14:06:05 +00:00
methodSignature: methodSignature,
methodDisplayName: methodDisplayName,
2015-01-27 13:05:06 +00:00
methodTypeName: methodTypeName,
2015-01-28 13:20:36 +00:00
getMethodWithName: getMethodWithName,
2015-01-28 13:39:10 +00:00
filterFunctions: filterFunctions,
filterEvents: filterEvents
2014-11-14 12:11:47 +00:00
};