Home Reference Source

packages/oo7-parity/src/utils/index.js

// (C) Copyright 2016-2017 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//         http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/* global parity */
const BigNumber = require('bignumber.js');
const oo7 = require('oo7');
const ParityApi = require('@parity/api');

const asciiToHex = ParityApi.util.asciiToHex;
const bytesToHex = ParityApi.util.bytesToHex;
const hexToAscii = ParityApi.util.hexToAscii;
const isAddressValid = h => oo7.Bond.instanceOf(h) ? h.map(ParityApi.util.isAddressValid) : ParityApi.util.isAddressValid(h);
const toChecksumAddress = h => oo7.Bond.instanceOf(h) ? h.map(ParityApi.util.toChecksumAddress) : ParityApi.util.toChecksumAddress(h);
const sha3 = h => oo7.Bond.instanceOf(h) ? h.map(ParityApi.util.sha3) : ParityApi.util.sha3(h);

const denominations = [ 'wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney', 'ether', 'grand', 'Mether', 'Gether', 'Tether', 'Pether', 'Eether', 'Zether', 'Yether', 'Nether', 'Dether', 'Vether', 'Uether' ];

// Parity Utilities
// TODO: move to parity.js, repackage or repot.

/**
 * Capitalizes the first letter of a string
 *
 * @param {string} s
 * @returns {string}
 */
function capitalizeFirstLetter (s) {
	return s.charAt(0).toUpperCase() + s.slice(1);
}

/**
 * Wrap `f` in a function that ensures it's called at most once.
 * The value returned from `f` is memoized and returned for all subsequent calls.
 *
 * @param {F} f
 * @returns {function(): F}
 */
function singleton (f) {
	var instance = null;
	return function () {
		if (instance === null) { instance = f(); }
		return instance;
	};
}

/**
 * Returns a {@link BigNumber} multiplier for give string denominator
 *
 * @param {string} denominator denominator (wei, eth, finney, Gwei, etc)
 * @returns {BigNumber} multiplier
 */
function denominationMultiplier (s) {
	let i = denominations.indexOf(s);
	if (i < 0) { throw new Error('Invalid denomination'); }
	return (new BigNumber(1000)).pow(i);
}

function interpretRender (s, defaultDenom = 6) {
	try {
		let m = s.toLowerCase().match(/([0-9,]+)(\.([0-9]*))? *([a-zA-Z]+)?/);
		let di = m[4] ? denominations.indexOf(m[4]) : defaultDenom;
		if (di === -1) {
			return null;
		}
		let n = (m[1].replace(',', '').replace(/^0*/, '')) || '0';
		let d = (m[3] || '').replace(/0*$/, '');
		return { denom: di, units: n, decimals: d, origNum: m[1] + (m[2] || ''), origDenom: m[4] || '' };
	} catch (e) {
		return null;
	}
}

function combineValue (v) {
	let d = (new BigNumber(1000)).pow(v.denom);
	let n = v.units;
	if (v.decimals) {
		n += v.decimals;
		d = d.div((new BigNumber(10)).pow(v.decimals.length));
	}
	return new BigNumber(n).mul(d);
}

/**
 * Add missing denominator to the value
 *
 * @param {BigNumber} v value
 * @param {string} d denominator
 * @returns {Value}
 */
function defDenom (v, d) {
	if (v.denom === null) {
		v.denom = d;
	}
	return v;
}

/**
 * Formats a value with denominator
 *
 * @param {Value} n value with denominator
 * @returns {string}
 */
function formatValue (n) {
	return `${formatValueNoDenom(n)} ${denominations[n.denom]}`;
}

/**
 * Format value without denominator
 * @param {Value} v
 * @returns {string}
 */
function formatValueNoDenom (n) {
	return `${n.units.toString().replace(/(\d)(?=(\d{3})+$)/g, '$1,')}${n.decimals ? '.' + n.decimals : ''}`;
}

/**
 * Format value without denominator
 *
 * @param {number|BigNumber} v
 * @param {number| exponent
 * @returns {string}
 */
function formatToExponential (v, n = 4) {
	return new BigNumber(v).toExponential(n);
}

function interpretQuantity (s) {
	try {
		let m = s.toLowerCase().match(/([0-9,]+)(\.([0-9]*))? *([a-zA-Z]+)?/);
		let d = denominationMultiplier(m[4] || 'ether');
		let n = +m[1].replace(',', '');
		if (m[2]) {
			n += m[3];
			for (let i = 0; i < m[3].length; ++i) {
				d = d.div(10);
			}
		}
		return new BigNumber(n).mul(d);
	} catch (e) {
		return null;
	}
}

/**
 * Split value into base and denominator
 *
 * @param {number|BigNumber} a
 * @returns {Value}
 */
function splitValue (a) {
	var i = 0;
	a = new BigNumber('' + a);
	if (a.gte(new BigNumber('10000000000000000')) && a.lt(new BigNumber('100000000000000000000000'))) {
		i = 6;
	} else {
		for (var aa = a; aa.gte(1000) && i < denominations.length - 1; aa = aa.div(1000)) { i++; }
	}

	for (var j = 0; j < i; ++j) { a = a.div(1000); }

	return { base: a, denom: i };
}

/**
 * Display balance into human-readable format with denomnator
 *
 * @param {string|BigNumber} balance
 * @returns {string}
 */
function formatBalance (n) {
	let a = splitValue(n);
	//	let b = Math.floor(a.base * 1000) / 1000;
	return `${a.base} ${denominations[a.denom]}`;
}

/**
 * Format block number into human-readable representation.
 * @param {string|number|BigNumber} blockNumber
 * @returns {string}
 */
function formatBlockNumber (n) {
	return '#' + ('' + n).replace(/(\d)(?=(\d{3})+$)/g, '$1,');
}

function isNullData (a) {
	return !a || typeof (a) !== 'string' || a.match(/^(0x)?0+$/) !== null;
}

function splitSignature (sig) {
	if ((sig.substr(2, 2) === '1b' || sig.substr(2, 2) === '1c') && (sig.substr(66, 2) !== '1b' && sig.substr(66, 2) !== '1c')) {
		// vrs
		return [sig.substr(0, 4), `0x${sig.substr(4, 64)}`, `0x${sig.substr(68, 64)}`];
	} else {
		// rsv
		return [`0x${sig.substr(130, 2)}`, `0x${sig.substr(2, 64)}`, `0x${sig.substr(66, 64)}`];
	}
}

function removeSigningPrefix (message) {
	if (!message.startsWith('\x19Ethereum Signed Message:\n')) {
		throw new Error('Invalid message - doesn\'t contain security prefix');
	}
	for (var i = 1; i < 6; ++i) {
		if (message.length === 26 + i + +message.substr(26, i)) {
			return message.substr(26 + i);
		}
	}
	throw new Error('Invalid message - invalid security prefix');
}

function cleanup (value, type = 'bytes32', api = parity.api) {
	// TODO: make work with arbitrary depth arrays
	if (value instanceof Array && type.match(/bytes[0-9]+/)) {
		// figure out if it's an ASCII string hiding in there:
		var ascii = '';
		for (var i = 0, ended = false; i < value.length && ascii !== null; ++i) {
			if (value[i] === 0) {
				ended = true;
			} else {
				ascii += String.fromCharCode(value[i]);
			}
			if ((ended && value[i] !== 0) || (!ended && (value[i] < 32 || value[i] >= 128))) {
				ascii = null;
			}
		}
		value = ascii === null ? '0x' + value.map(n => ('0' + n.toString(16)).slice(-2)).join('') : ascii;
	}
	if (type.substr(0, 4) === 'uint' && +type.substr(4) <= 48) {
		value = +value;
	}
	return value;
}

module.exports = {
	asciiToHex,
	bytesToHex,
	hexToAscii,
	isAddressValid,
	toChecksumAddress,
	sha3,
	capitalizeFirstLetter,
	singleton,
	denominations,
	denominationMultiplier,
	interpretRender,
	combineValue,
	defDenom,
	formatValue,
	formatValueNoDenom,
	formatToExponential,
	interpretQuantity,
	splitValue,
	formatBalance,
	formatBlockNumber,
	isNullData,
	splitSignature,
	removeSigningPrefix,
	cleanup
};