mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-20 19:15:54 +00:00
474 lines
11 KiB
JavaScript
474 lines
11 KiB
JavaScript
// Represent Ripple amounts and currencies.
|
|
// - Numbers in hex are big-endian.
|
|
|
|
var utils = require('./utils.js');
|
|
var jsbn = require('./jsbn.js');
|
|
|
|
var BigInteger = jsbn.BigInteger;
|
|
|
|
var accounts = {};
|
|
|
|
var setAccounts = function (accounts_new) {
|
|
accounts = accounts_new;
|
|
};
|
|
|
|
var UInt160 = function () {
|
|
// Internal form:
|
|
// 0, 1, 'iXXXXX', 20 byte string, or NaN.
|
|
// XXX Should standardize on 'i' format or 20 format.
|
|
this.value = NaN;
|
|
};
|
|
|
|
UInt160.json_rewrite = function (j) {
|
|
return UInt160.from_json(j).to_json();
|
|
};
|
|
|
|
// Return a new UInt160 from j.
|
|
UInt160.from_json = function (j) {
|
|
return 'string' === typeof j
|
|
? (new UInt160()).parse_json(j)
|
|
: j.clone();
|
|
};
|
|
|
|
UInt160.prototype.clone = function() {
|
|
return this.copyTo(new UInt160());
|
|
};
|
|
|
|
// Returns copy.
|
|
UInt160.prototype.copyTo = function(d) {
|
|
d.value = this.value;
|
|
|
|
return d;
|
|
};
|
|
|
|
// value = NaN on error.
|
|
UInt160.prototype.parse_json = function (j) {
|
|
// Canonicalize and validate
|
|
if (j in accounts)
|
|
j = accounts[j].account;
|
|
|
|
switch (j) {
|
|
case undefined:
|
|
case "0":
|
|
case exports.consts.address_xns:
|
|
case exports.consts.uint160_xns:
|
|
case exports.consts.hex_xns:
|
|
this.value = 0;
|
|
break;
|
|
|
|
case "1":
|
|
case exports.consts.address_one:
|
|
case exports.consts.uint160_one:
|
|
case exports.consts.hex_one:
|
|
this.value = 1;
|
|
break;
|
|
|
|
default:
|
|
if ('string' !== typeof j) {
|
|
this.value = NaN;
|
|
}
|
|
else if (20 === j.length) {
|
|
this.value = j;
|
|
}
|
|
else if (40 === j.length) {
|
|
this.value = utils.hexToString(j);
|
|
}
|
|
else if (j[0] === "r") {
|
|
// XXX Do more checking convert to string.
|
|
|
|
this.value = j;
|
|
}
|
|
else {
|
|
this.value = NaN;
|
|
}
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
// Convert from internal form.
|
|
// XXX Json form should allow 0 and 1, C++ doesn't currently allow it.
|
|
UInt160.prototype.to_json = function () {
|
|
if ("0" === this.value) {
|
|
return exports.consts.hex_xns;
|
|
}
|
|
else if ("1" === this.value)
|
|
{
|
|
return exports.consts.hex_one;
|
|
}
|
|
else if ('string' === typeof this.value && 20 === this.value.length) {
|
|
return utils.stringToHex(this.value);
|
|
}
|
|
else
|
|
{
|
|
return this.value;
|
|
}
|
|
};
|
|
|
|
var Currency = function () {
|
|
// Internal form: 0 = XNS. 3 letter-code.
|
|
// XXX Internal should be 0 or hex.
|
|
|
|
// Json form:
|
|
// '', 'XNS', '0': 0
|
|
// 3-letter code: ...
|
|
// XXX Should support hex, C++ doesn't currently allow it.
|
|
|
|
this.value = NaN;
|
|
}
|
|
|
|
Currency.from_json = function (j) {
|
|
return (new Currency()).parse_json(j);
|
|
};
|
|
|
|
Currency.prototype.clone = function() {
|
|
return this.copyTo(new Currency());
|
|
};
|
|
|
|
// Returns copy.
|
|
Currency.prototype.copyTo = function(d) {
|
|
d.value = this.value;
|
|
|
|
return d;
|
|
};
|
|
|
|
// this.value = NaN on error.
|
|
Currency.prototype.parse_json = function(j) {
|
|
if ("" === j || "0" === j || "XNS" === j) {
|
|
this.value = 0;
|
|
}
|
|
else if ('string' != typeof j || 3 !== j.length) {
|
|
this.value = NaN;
|
|
}
|
|
else {
|
|
this.value = j;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Currency.prototype.to_json = function () {
|
|
return this.value ? this.value : "XNS";
|
|
};
|
|
|
|
Currency.prototype.to_human = function() {
|
|
return this.value ? this.value : "XNS";
|
|
};
|
|
|
|
var Amount = function () {
|
|
// Json format:
|
|
// integer : XNS
|
|
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
|
|
|
|
this.value = new BigInteger(); // NaN for bad value. Always positive for non-XNS.
|
|
this.offset = undefined; // For non-XNS.
|
|
this.is_native = true; // Default to XNS. Only valid if value is not NaN.
|
|
this.is_negative = undefined; // Undefined for XNS.
|
|
|
|
this.currency = new Currency();
|
|
this.issuer = new UInt160();
|
|
};
|
|
|
|
Amount.json_rewrite = function(j) {
|
|
return Amount.from_json(j).to_json();
|
|
};
|
|
|
|
Amount.from_json = function(j) {
|
|
return (new Amount()).parse_json(j);
|
|
};
|
|
|
|
Amount.prototype.clone = function() {
|
|
return this.copyTo(new Amount());
|
|
};
|
|
|
|
// Returns copy.
|
|
Amount.prototype.copyTo = function(d) {
|
|
if ('object' === typeof this.value)
|
|
{
|
|
this.value.copyTo(d.value);
|
|
}
|
|
else
|
|
{
|
|
d.value = this.value;
|
|
}
|
|
|
|
d.offset = this.offset;
|
|
d.is_native = this.is_native;
|
|
d.is_negative = this.is_negative;
|
|
|
|
this.currency.copyTo(d.currency);
|
|
this.issuer.copyTo(d.issuer);
|
|
|
|
return d;
|
|
};
|
|
|
|
// YYY Might also provide is_valid_json.
|
|
Amount.prototype.is_valid = function() {
|
|
return !isNaN(this.value);
|
|
};
|
|
|
|
// Convert only value to JSON wire format.
|
|
Amount.prototype.to_text = function(allow_nan) {
|
|
if (isNaN(this.value)) {
|
|
// Never should happen.
|
|
return allow_nan ? NaN : "0";
|
|
}
|
|
else if (this.is_native) {
|
|
if (this.value.compareTo(exports.consts.bi_xns_max) > 0 || this.value.compareTo(exports.consts.bi_xns_min) < 0)
|
|
{
|
|
// Never should happen.
|
|
return allow_nan ? NaN : "0";
|
|
}
|
|
else
|
|
{
|
|
return this.value.toString();
|
|
}
|
|
}
|
|
else if (this.value.equals(BigInteger.ZERO))
|
|
{
|
|
return "0";
|
|
}
|
|
else if (this.offset < -25 || this.offset > -5)
|
|
{
|
|
// Use e notation.
|
|
// XXX Clamp output.
|
|
|
|
return (this.is_negative ? "-" : "") + this.value.toString() + "e" + this.offset;
|
|
}
|
|
else
|
|
{
|
|
var val = "000000000000000000000000000" + this.value.toString() + "00000000000000000000000";
|
|
var pre = val.substring(0, this.offset + 43);
|
|
var post = val.substring(this.offset + 43);
|
|
var s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
|
|
var s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
|
|
|
|
return (this.is_negative ? "-" : "")
|
|
+ (s_pre ? s_pre[0] : "0")
|
|
+ (s_post ? "." + post.substring(0, 1+post.length-s_post[0].length) : "");
|
|
}
|
|
};
|
|
|
|
Amount.prototype.canonicalize = function() {
|
|
if (isNaN(this.value) || !this.currency) {
|
|
// nothing
|
|
}
|
|
else if (this.value.equals(BigInteger.ZERO)) {
|
|
this.offset = -100;
|
|
this.is_negative = false;
|
|
}
|
|
else
|
|
{
|
|
while (this.value.compareTo(exports.consts.bi_man_min_value) < 0) {
|
|
this.value = this.value.multiply(exports.consts.bi_10);
|
|
this.offset -= 1;
|
|
}
|
|
|
|
while (this.value.compareTo(exports.consts.bi_man_max_value) > 0) {
|
|
this.value = this.value.divide(exports.consts.bi_10);
|
|
this.offset += 1;
|
|
}
|
|
}
|
|
};
|
|
|
|
Amount.prototype.negate = function () {
|
|
if (this.is_native) {
|
|
this.value.negate();
|
|
}
|
|
else {
|
|
this.is_negative = !this.is_negative;
|
|
}
|
|
};
|
|
|
|
Amount.prototype.to_json = function() {
|
|
if (this.is_native) {
|
|
return this.to_text();
|
|
}
|
|
else
|
|
{
|
|
return {
|
|
'value' : this.to_text(),
|
|
'currency' : this.currency.to_json(),
|
|
'issuer' : this.issuer.to_json(),
|
|
};
|
|
}
|
|
};
|
|
|
|
Amount.prototype.to_text_full = function() {
|
|
return isNaN(this.value)
|
|
? NaN
|
|
: this.is_native
|
|
? this.to_text() + "/XNS"
|
|
: this.to_text() + "/" + this.currency.to_json() + "/" + this.issuer.to_json();
|
|
};
|
|
|
|
// Parse a XNS value from untrusted input.
|
|
// - integer = raw units
|
|
// - float = with precision 6
|
|
// XXX Improvements: disallow leading zeros.
|
|
Amount.prototype.parse_native = function(j) {
|
|
var m;
|
|
|
|
if ('string' === typeof j)
|
|
m = j.match(/^(\d+)(\.\d{1,6})?$/);
|
|
|
|
if (m) {
|
|
if (undefined === m[2]) {
|
|
// Integer notation
|
|
|
|
this.value = new BigInteger(m[1]);
|
|
}
|
|
else {
|
|
// Decimal notation
|
|
|
|
var int_part = (new BigInteger(m[1])).multiply(exports.consts.bi_xns_unit);
|
|
var fraction_part = (new BigInteger(m[2])).multiply(new BigInteger(String(Math.pow(10, 1+exports.consts.xns_precision-m[2].length))));
|
|
|
|
this.value = int_part.add(fraction_part);
|
|
}
|
|
|
|
this.is_native = true;
|
|
this.offset = undefined;
|
|
this.is_negative = undefined;
|
|
|
|
if (this.value.compareTo(exports.consts.bi_xns_max) > 0 || this.value.compareTo(exports.consts.bi_xns_min) < 0)
|
|
{
|
|
this.value = NaN;
|
|
}
|
|
}
|
|
else {
|
|
this.value = NaN;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
// Parse a non-native value.
|
|
Amount.prototype.parse_value = function(j) {
|
|
this.is_native = false;
|
|
|
|
if ('number' === typeof j) {
|
|
this.is_negative = j < 0;
|
|
if (this.is_negative) j = -j;
|
|
this.value = new BigInteger(j);
|
|
this.offset = 0;
|
|
|
|
this.canonicalize();
|
|
}
|
|
else if ('string' === typeof j) {
|
|
var i = j.match(/^(-?)(\d+)$/);
|
|
var d = !i && j.match(/^(-?)(\d+)\.(\d*)$/);
|
|
var e = !e && j.match(/^(-?)(\d+)e(\d+)$/);
|
|
|
|
if (e) {
|
|
// e notation
|
|
|
|
this.value = new BigInteger(e[2]);
|
|
this.offset = parseInt(e[3]);
|
|
this.is_negative = !!e[1];
|
|
|
|
this.canonicalize();
|
|
}
|
|
else if (d) {
|
|
// float notation
|
|
|
|
var integer = new BigInteger(d[2]);
|
|
var fraction = new BigInteger(d[3]);
|
|
var precision = d[3].length;
|
|
|
|
this.value = integer.multiply(exports.consts.bi_10.clone().pow(precision)).add(fraction);
|
|
this.offset = -precision;
|
|
this.is_negative = !!d[1];
|
|
|
|
this.canonicalize();
|
|
}
|
|
else if (i) {
|
|
// integer notation
|
|
|
|
this.value = new BigInteger(i[2]);
|
|
this.offset = 0;
|
|
this.is_negative = !!i[1];
|
|
|
|
this.canonicalize();
|
|
}
|
|
else {
|
|
this.value = NaN;
|
|
}
|
|
}
|
|
else if (j.constructor == BigInteger) {
|
|
this.value = j.clone();
|
|
}
|
|
else {
|
|
this.value = NaN;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
// <-> j
|
|
Amount.prototype.parse_json = function(j) {
|
|
if ('string' === typeof j) {
|
|
// .../.../... notation is not a wire format. But allowed for easier testing.
|
|
var m = j.match(/^(.+)\/(...)\/(.+)$/);
|
|
|
|
if (m) {
|
|
this.parse_value(m[1]);
|
|
this.currency = Currency.from_json(m[2]);
|
|
this.issuer = UInt160.from_json(m[3]);
|
|
}
|
|
else {
|
|
this.parse_native(j);
|
|
this.currency = new Currency();
|
|
this.issuer = new UInt160();
|
|
}
|
|
}
|
|
else if ('object' === typeof j && j.constructor == Amount) {
|
|
j.copyTo(this);
|
|
}
|
|
else if ('object' === typeof j && 'value' in j) {
|
|
// Parse the passed value to sanitize and copy it.
|
|
|
|
this.parse_value(j.value);
|
|
this.currency.parse_json(j.currency); // Never XNS.
|
|
this.issuer.parse_json(j.issuer);
|
|
}
|
|
else {
|
|
this.value = NaN;
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Amount.prototype.parse_issuer = function (issuer) {
|
|
this.issuer.parse_json(issuer);
|
|
|
|
return this;
|
|
};
|
|
|
|
exports.setAccounts = setAccounts;
|
|
exports.Amount = Amount;
|
|
exports.Currency = Currency;
|
|
exports.UInt160 = UInt160;
|
|
|
|
exports.consts = {
|
|
'address_xns' : "rrrrrrrrrrrrrrrrrrrrrhoLvTp",
|
|
'address_one' : "rrrrrrrrrrrrrrrrrrrrBZbvji",
|
|
'currency_xns' : 0,
|
|
'currency_one' : 1,
|
|
'uint160_xns' : utils.hexToString("0000000000000000000000000000000000000000"),
|
|
'uint160_one' : utils.hexToString("0000000000000000000000000000000000000001"),
|
|
'hex_xns' : "0000000000000000000000000000000000000000",
|
|
'hex_one' : "0000000000000000000000000000000000000001",
|
|
'xns_precision' : 6,
|
|
|
|
// BigInteger values prefixed with bi_.
|
|
'bi_10' : new BigInteger('10'),
|
|
'bi_man_max_value' : new BigInteger('9999999999999999'),
|
|
'bi_man_min_value' : new BigInteger('1000000000000000'),
|
|
'bi_xns_max' : new BigInteger("9000000000000000000"), // Json wire limit.
|
|
'bi_xns_min' : new BigInteger("-9000000000000000000"), // Json wire limit.
|
|
'bi_xns_unit' : new BigInteger('1000000'),
|
|
};
|
|
|
|
// vim:sw=2:sts=2:ts=8
|