|
|
|
/*
|
|
|
|
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>
|
|
|
|
* Gav Wood <g@ethdev.com>
|
|
|
|
* @date 2014
|
|
|
|
*/
|
|
|
|
|
|
|
|
var web3 = require('./web3');
|
|
|
|
var utils = require('./utils');
|
|
|
|
var types = require('./types');
|
|
|
|
var c = require('./const');
|
|
|
|
var f = require('./formatters');
|
|
|
|
|
|
|
|
var displayTypeError = function (type) {
|
|
|
|
console.error('parser does not support type: ' + type);
|
|
|
|
};
|
|
|
|
|
|
|
|
/// This method should be called if we want to check if givent type is an array type
|
|
|
|
/// @returns true if it is, otherwise false
|
|
|
|
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.
|
|
|
|
return f.formatInputInt(value.length);
|
|
|
|
return "";
|
|
|
|
};
|
|
|
|
|
|
|
|
var inputTypes = types.inputTypes();
|
|
|
|
|
|
|
|
/// Formats input params to bytes
|
|
|
|
/// @param abi contract method inputs
|
|
|
|
/// @param array of params that will be formatted to bytes
|
|
|
|
/// @returns bytes representation of input params
|
|
|
|
var formatInput = function (inputs, params) {
|
|
|
|
var bytes = "";
|
|
|
|
var padding = c.ETH_PADDING * 2;
|
|
|
|
|
|
|
|
/// first we iterate in search for dynamic
|
|
|
|
inputs.forEach(function (input, index) {
|
|
|
|
bytes += dynamicTypeBytes(input.type, params[index]);
|
|
|
|
});
|
|
|
|
|
|
|
|
inputs.forEach(function (input, i) {
|
|
|
|
var typeMatch = false;
|
|
|
|
for (var j = 0; j < inputTypes.length && !typeMatch; j++) {
|
|
|
|
typeMatch = inputTypes[j].type(inputs[i].type, params[i]);
|
|
|
|
}
|
|
|
|
if (!typeMatch) {
|
|
|
|
displayTypeError(inputs[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
var formatter = inputTypes[j - 1].format;
|
|
|
|
var toAppend = "";
|
|
|
|
|
|
|
|
if (arrayType(inputs[i].type))
|
|
|
|
toAppend = params[i].reduce(function (acc, curr) {
|
|
|
|
return acc + formatter(curr);
|
|
|
|
}, "");
|
|
|
|
else
|
|
|
|
toAppend = formatter(params[i]);
|
|
|
|
|
|
|
|
bytes += toAppend;
|
|
|
|
});
|
|
|
|
return bytes;
|
|
|
|
};
|
|
|
|
|
|
|
|
var dynamicBytesLength = function (type) {
|
|
|
|
if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.
|
|
|
|
return c.ETH_PADDING * 2;
|
|
|
|
return 0;
|
|
|
|
};
|
|
|
|
|
|
|
|
var outputTypes = types.outputTypes();
|
|
|
|
|
|
|
|
/// Formats output bytes back to param list
|
|
|
|
/// @param contract abi method outputs
|
|
|
|
/// @param bytes representtion of output
|
|
|
|
/// @returns array of output params
|
|
|
|
var formatOutput = function (outs, output) {
|
|
|
|
|
|
|
|
output = output.slice(2);
|
|
|
|
var result = [];
|
|
|
|
var padding = c.ETH_PADDING * 2;
|
|
|
|
|
|
|
|
var dynamicPartLength = outs.reduce(function (acc, curr) {
|
|
|
|
return acc + dynamicBytesLength(curr.type);
|
|
|
|
}, 0);
|
|
|
|
|
|
|
|
var dynamicPart = output.slice(0, dynamicPartLength);
|
|
|
|
output = output.slice(dynamicPartLength);
|
|
|
|
|
|
|
|
outs.forEach(function (out, i) {
|
|
|
|
var typeMatch = false;
|
|
|
|
for (var j = 0; j < outputTypes.length && !typeMatch; j++) {
|
|
|
|
typeMatch = outputTypes[j].type(outs[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!typeMatch) {
|
|
|
|
displayTypeError(outs[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
var formatter = outputTypes[j - 1].format;
|
|
|
|
if (arrayType(outs[i].type)) {
|
|
|
|
var size = f.formatOutputUInt(dynamicPart.slice(0, padding));
|
|
|
|
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 (types.prefixedType('string')(outs[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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// @param json abi for contract
|
|
|
|
/// @returns input parser object for given json abi
|
|
|
|
/// TODO: refactor creating the parser, do not double logic from contract
|
|
|
|
var inputParser = function (json) {
|
|
|
|
var parser = {};
|
|
|
|
json.forEach(function (method) {
|
|
|
|
var displayName = utils.extractDisplayName(method.name);
|
|
|
|
var typeName = utils.extractTypeName(method.name);
|
|
|
|
|
|
|
|
var impl = function () {
|
|
|
|
var params = Array.prototype.slice.call(arguments);
|
|
|
|
return formatInput(method.inputs, params);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (parser[displayName] === undefined) {
|
|
|
|
parser[displayName] = impl;
|
|
|
|
}
|
|
|
|
|
|
|
|
parser[displayName][typeName] = impl;
|
|
|
|
});
|
|
|
|
|
|
|
|
return parser;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// @param json abi for contract
|
|
|
|
/// @returns output parser for given json abi
|
|
|
|
var outputParser = function (json) {
|
|
|
|
var parser = {};
|
|
|
|
json.forEach(function (method) {
|
|
|
|
|
|
|
|
var displayName = utils.extractDisplayName(method.name);
|
|
|
|
var typeName = utils.extractTypeName(method.name);
|
|
|
|
|
|
|
|
var impl = function (output) {
|
|
|
|
return formatOutput(method.outputs, output);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (parser[displayName] === undefined) {
|
|
|
|
parser[displayName] = impl;
|
|
|
|
}
|
|
|
|
|
|
|
|
parser[displayName][typeName] = impl;
|
|
|
|
});
|
|
|
|
|
|
|
|
return parser;
|
|
|
|
};
|
|
|
|
|
|
|
|
/// @param function/event name for which we want to get signature
|
|
|
|
/// @returns signature of function/event with given name
|
|
|
|
var signatureFromAscii = function (name) {
|
|
|
|
return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2);
|
|
|
|
};
|
|
|
|
|
|
|
|
var eventSignatureFromAscii = function (name) {
|
|
|
|
return web3.sha3(web3.fromAscii(name));
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
inputParser: inputParser,
|
|
|
|
outputParser: outputParser,
|
|
|
|
formatInput: formatInput,
|
|
|
|
formatOutput: formatOutput,
|
|
|
|
signatureFromAscii: signatureFromAscii,
|
|
|
|
eventSignatureFromAscii: eventSignatureFromAscii
|
|
|
|
};
|
|
|
|
|