/*
This file is part of web3.js.
web3.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.
web3.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 web3.js. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* @file soliditySha3.js
* @author Fabian Vogelsteller <fabian@ethereum.org>
* @date 2017
*/
var _ = require('underscore');
var BN = require('bn.js');
var utils = require('./utils.js');
var _elementaryName = function (name) {
if (name.startsWith('int[')) {
return 'int256' + name.slice(3);
} else if (name === 'int') {
return 'int256';
} else if (name.startsWith('uint[')) {
return 'uint256' + name.slice(4);
} else if (name === 'uint') {
return 'uint256';
} else Iif (name.startsWith('fixed[')) {
return 'fixed128x128' + name.slice(5);
} else Iif (name === 'fixed') {
return 'fixed128x128';
} else Iif (name.startsWith('ufixed[')) {
return 'ufixed128x128' + name.slice(6);
} else Iif (name === 'ufixed') {
return 'ufixed128x128';
}
return name;
};
// Parse N from type<N>
var _parseTypeN = function (type) {
var typesize = /^\D+(\d+).*$/.exec(type);
return typesize ? parseInt(typesize[1], 10) : null;
};
// Parse N from type[<N>]
var _parseTypeNArray = function (type) {
var arraySize = /^\D+\d*\[(\d+)\]$/.exec(type);
return arraySize ? parseInt(arraySize[1], 10) : null;
};
var _parseNumber = function (arg) {
var type = typeof arg;
if (type === 'string') {
if (utils.isHexStrict(arg)) {
return new BN(arg.replace(/0x/i,''), 16);
} else {
return new BN(arg, 10);
}
} else if (type === 'number') {
return new BN(arg);
} else Iif (utils.isBigNumber(arg)) {
return new BN(arg.toString(10));
} else Eif (utils.isBN(arg)) {
return arg;
} else {
throw new Error(arg +' is not a number');
}
};
/* eslint-disable complexity */
var _solidityPack = function (type, value, arraySize) {
var size, num;
type = _elementaryName(type);
if (type === 'bytes') {
if (value.replace(/^0x/i,'').length % 2 !== 0) {
throw new Error('Invalid bytes characters '+ value.length);
}
return value;
} else if (type === 'string') {
return utils.utf8ToHex(value);
} else if (type === 'bool') {
return value ? '01' : '00';
} else if (type.startsWith('address')) {
if(arraySize) {
size = 64;
} else {
size = 40;
}
if(!utils.isAddress(value)) {
throw new Error(value +' is not a valid address, or the checksum is invalid.');
}
return utils.leftPad(value.toLowerCase(), size);
}
size = _parseTypeN(type);
if (type.startsWith('bytes')) {
Iif(!size) {
throw new Error('bytes[] not yet supported in solidity');
}
// must be 32 byte slices when in an array
if(arraySize) {
size = 32;
}
if (size < 1 || size > 32 || size < value.replace(/^0x/i,'').length / 2 ) {
throw new Error('Invalid bytes' + size +' for '+ value);
}
return utils.rightPad(value, size * 2);
} else if (type.startsWith('uint')) {
Iif ((size % 8) || (size < 8) || (size > 256)) {
throw new Error('Invalid uint'+size+' size');
}
num = _parseNumber(value);
Iif (num.bitLength() > size) {
throw new Error('Supplied uint exceeds width: ' + size + ' vs ' + num.bitLength());
}
Iif(num.lt(new BN(0))) {
throw new Error('Supplied uint '+ num.toString() +' is negative');
}
return size ? utils.leftPad(num.toString('hex'), size/8 * 2) : num;
} else Eif (type.startsWith('int')) {
Iif ((size % 8) || (size < 8) || (size > 256)) {
throw new Error('Invalid int'+size+' size');
}
num = _parseNumber(value);
Iif (num.bitLength() > size) {
throw new Error('Supplied int exceeds width: ' + size + ' vs ' + num.bitLength());
}
if(num.lt(new BN(0))) {
return num.toTwos(size).toString('hex');
} else {
return size ? utils.leftPad(num.toString('hex'), size/8 * 2) : num;
}
} else {
// FIXME: support all other types
throw new Error('Unsupported or invalid type: ' + type);
}
};
/* eslint-enable complexity */
/* eslint-disable complexity */
var _processSoliditySha3Args = function (arg) {
if(_.isArray(arg)) {
throw new Error('Autodetection of array types is not supported.');
}
var type, value = '';
var hexArg, arraySize;
// if type is given
if (_.isObject(arg) && (arg.hasOwnProperty('v') || arg.hasOwnProperty('t') || arg.hasOwnProperty('value') || arg.hasOwnProperty('type'))) {
type = arg.t || arg.type;
value = arg.v || arg.value;
// otherwise try to guess the type
} else {
type = utils.toHex(arg, true);
value = utils.toHex(arg);
if (!type.startsWith('int') && !type.startsWith('uint')) {
type = 'bytes';
}
}
if ((type.startsWith('int') || type.startsWith('uint')) && typeof value === 'string' && !/^(-)?0x/i.test(value)) {
value = new BN(value);
}
// get the array size
if(_.isArray(value)) {
arraySize = _parseTypeNArray(type);
if(arraySize && value.length !== arraySize) {
throw new Error(type +' is not matching the given array '+ JSON.stringify(value));
} else {
arraySize = value.length;
}
}
if (_.isArray(value)) {
hexArg = value.map(function (val) {
return _solidityPack(type, val, arraySize).toString('hex').replace('0x','');
});
return hexArg.join('');
} else {
hexArg = _solidityPack(type, value, arraySize);
return hexArg.toString('hex').replace('0x','');
}
};
/* eslint-enable complexity */
/**
* Hashes solidity values to a sha3 hash using keccak 256
*
* @method soliditySha3
* @return {Object} the sha3
*/
var soliditySha3 = function () {
var args = Array.prototype.slice.call(arguments);
var hexArgs = _.map(args, _processSoliditySha3Args);
return utils.sha3('0x'+ hexArgs.join(''));
};
module.exports = soliditySha3;
|