mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 04:05:52 +00:00
Currency: Add support for complex currencies. (UInt160)
This patch might regress the performance of the Currency class and by extension the Amount class. Since Amount is on a lot of hot paths in the client we should make sure this isn't a major problem. As for compatibility, this patch is a major change, but it should maintain the public interface very well, which the exception of some strange edge cases (e.g. Currency.from_json(1337)), which weren't well-defined before anyway. Any code that accesses _value directly (shame on you!) will need to be fixed. There aren't any such references in ripple-client or the rippled test suite, so I think we're looking pretty good.
This commit is contained in:
@@ -628,7 +628,7 @@ Amount.prototype.parse_json = function (j) {
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
// .../.../... notation is not a wire format. But allowed for easier testing.
|
||||
var m = j.match(/^([^/]+)\/(...)(?:\/(.+))?$/);
|
||||
var m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||
|
||||
if (m) {
|
||||
this._currency = Currency.from_json(m[2]);
|
||||
@@ -655,7 +655,7 @@ Amount.prototype.parse_json = function (j) {
|
||||
j.copyTo(this);
|
||||
} else if (j.hasOwnProperty('value')) {
|
||||
// Parse the passed value to sanitize and copy it.
|
||||
this._currency.parse_json(j.currency); // Never XRP.
|
||||
this._currency.parse_json(j.currency, true); // Never XRP.
|
||||
|
||||
if (typeof j.issuer === 'string') {
|
||||
this._issuer.parse_json(j.issuer);
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
|
||||
var extend = require('extend');
|
||||
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var utils = require('./utils');
|
||||
|
||||
//
|
||||
// Currency support
|
||||
//
|
||||
|
||||
// XXX Internal form should be UInt160.
|
||||
function Currency() {
|
||||
var Currency = extend(function () {
|
||||
// Internal form: 0 = XRP. 3 letter-code.
|
||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
||||
|
||||
@@ -14,72 +18,63 @@ function Currency() {
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
};
|
||||
}, UInt160);
|
||||
|
||||
// Given "USD" return the json.
|
||||
Currency.json_rewrite = function (j) {
|
||||
return Currency.from_json(j).to_json();
|
||||
};
|
||||
Currency.prototype = extend({}, UInt160.prototype);
|
||||
Currency.prototype.constructor = Currency;
|
||||
|
||||
Currency.from_json = function (j) {
|
||||
return j instanceof Currency ? j.clone() : new Currency().parse_json(j);
|
||||
};
|
||||
Currency.HEX_CURRENCY_BAD = "0000000000000000000000005852500000000000";
|
||||
|
||||
Currency.from_bytes = function (j) {
|
||||
return j instanceof Currency ? j.clone() : new Currency().parse_bytes(j);
|
||||
};
|
||||
|
||||
Currency.is_valid = function (j) {
|
||||
return Currency.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
Currency.prototype.clone = function() {
|
||||
return this.copyTo(new Currency());
|
||||
};
|
||||
|
||||
// Returns copy.
|
||||
Currency.prototype.copyTo = function (d) {
|
||||
d._value = this._value;
|
||||
return d;
|
||||
};
|
||||
|
||||
Currency.prototype.equals = function (d) {
|
||||
var equals = (typeof this._value !== 'string' && isNaN(this._value))
|
||||
|| (typeof d._value !== 'string' && isNaN(d._value));
|
||||
return equals ? false: this._value === d._value;
|
||||
Currency.from_json = function (j, shouldInterpretXrpAsIou) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_json(j, shouldInterpretXrpAsIou);
|
||||
}
|
||||
};
|
||||
|
||||
// this._value = NaN on error.
|
||||
Currency.prototype.parse_json = function (j) {
|
||||
var result = NaN;
|
||||
Currency.prototype.parse_json = function (j, shouldInterpretXrpAsIou) {
|
||||
this._value = NaN;
|
||||
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
if (!j || /^(0|XRP)$/.test(j)) {
|
||||
result = 0;
|
||||
if (shouldInterpretXrpAsIou) {
|
||||
this.parse_hex(Currency.HEX_CURRENCY_BAD);
|
||||
} else {
|
||||
this.parse_hex(Currency.HEX_ZERO);
|
||||
}
|
||||
} else if (/^[a-zA-Z0-9]{3}$/.test(j)) {
|
||||
result = j;
|
||||
var currencyCode = j.toUpperCase();
|
||||
var currencyData = utils.arraySet(20, 0);
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
this.parse_bytes(currencyData);
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
result = j;
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (j instanceof Currency) {
|
||||
result = j.copyTo({})._value;
|
||||
this._value = j.copyTo({})._value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
this._value = result;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// XXX Probably not needed anymore?
|
||||
/*
|
||||
Currency.prototype.parse_bytes = function (byte_array) {
|
||||
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
||||
var result;
|
||||
@@ -110,21 +105,62 @@ Currency.prototype.parse_bytes = function (byte_array) {
|
||||
}
|
||||
return this;
|
||||
};
|
||||
*/
|
||||
|
||||
Currency.prototype.is_native = function () {
|
||||
return !isNaN(this._value) && !this._value;
|
||||
return !isNaN(this._value) && this.is_zero();
|
||||
};
|
||||
|
||||
Currency.prototype.is_valid = function () {
|
||||
return typeof this._value === 'string' || !isNaN(this._value);
|
||||
};
|
||||
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||
//
|
||||
// We could be doing further checks into the internal format of the
|
||||
// currency data, since there are some values that are invalid.
|
||||
//
|
||||
//Currency.prototype.is_valid = function () {
|
||||
// return this._value instanceof BigInteger && ...;
|
||||
//};
|
||||
|
||||
Currency.prototype.to_json = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
// is it 0 everywhere except 12, 13, 14?
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
if (!bytes) {
|
||||
return "XRP";
|
||||
}
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
var currencyCode = String.fromCharCode(bytes[12])
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
if (/^[A-Z0-9]{3}$/.test(currencyCode) && currencyCode !== "XRP" ) {
|
||||
return currencyCode;
|
||||
} else if (currencyCode === "\0\0\0") {
|
||||
return "XRP";
|
||||
} else {
|
||||
return "XRP";
|
||||
}
|
||||
} else {
|
||||
var currencyHex = this.to_hex();
|
||||
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very odd
|
||||
// behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
if (currencyHex === Currency.HEX_ONE) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return currencyHex;
|
||||
}
|
||||
};
|
||||
|
||||
Currency.prototype.to_human = function () {
|
||||
return this._value ? this._value : "XRP";
|
||||
return this.to_json();
|
||||
};
|
||||
|
||||
exports.Currency = Currency;
|
||||
|
||||
@@ -280,22 +280,13 @@ STHash160.id = 17;
|
||||
// Internal
|
||||
var STCurrency = new SerializedType({
|
||||
serialize: function (so, val, xrp_as_ascii) {
|
||||
var currency = val.to_json().toUpperCase();
|
||||
var currencyData = val.to_bytes();
|
||||
|
||||
if (!isCurrencyString(currency)) {
|
||||
if (!currencyData) {
|
||||
throw new Error('Tried to serialize invalid/unimplemented currency type.');
|
||||
}
|
||||
|
||||
if (currency === 'XRP' && !xrp_as_ascii) {
|
||||
serialize_hex(so, UInt160.HEX_ZERO, true);
|
||||
} else {
|
||||
var currencyCode = currency.toUpperCase();
|
||||
var currencyData = utils.arraySet(20, 0);
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
currencyData[13] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[14] = currencyCode.charCodeAt(2) & 0xff;
|
||||
so.append(currencyData);
|
||||
}
|
||||
},
|
||||
parse: function (so) {
|
||||
var bytes = so.read(20);
|
||||
@@ -484,8 +475,8 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
}
|
||||
|
||||
if (entry.currency) {
|
||||
var currency = Currency.from_json(entry.currency);
|
||||
STCurrency.serialize(so, currency, entry.non_native);
|
||||
var currency = Currency.from_json(entry.currency, entry.non_native);
|
||||
STCurrency.serialize(so, currency);
|
||||
}
|
||||
|
||||
if (entry.issuer) {
|
||||
@@ -544,7 +535,8 @@ var STPathSet = exports.PathSet = new SerializedType({
|
||||
}
|
||||
if (tag_byte & this.typeIssuer) {
|
||||
//console.log('entry.issuer');
|
||||
entry.issuer = STHash160.parse(so); //should know to use Base58?
|
||||
entry.issuer = STHash160.parse(so);
|
||||
// Enable and set correct type of base-58 encoding
|
||||
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||
//console.log('DONE WITH ISSUER!');
|
||||
}
|
||||
|
||||
@@ -75,6 +75,15 @@ UInt.from_bn = function (j) {
|
||||
}
|
||||
};
|
||||
|
||||
// Return a new UInt from j.
|
||||
UInt.from_number = function (j) {
|
||||
if (j instanceof this) {
|
||||
return j.clone();
|
||||
} else {
|
||||
return (new this()).parse_number(j);
|
||||
}
|
||||
};
|
||||
|
||||
UInt.is_valid = function (j) {
|
||||
return this.from_json(j).is_valid();
|
||||
};
|
||||
@@ -192,6 +201,19 @@ UInt.prototype.parse_bn = function (j) {
|
||||
return this;
|
||||
};
|
||||
|
||||
UInt.prototype.parse_number = function (j) {
|
||||
this._value = NaN;
|
||||
|
||||
if ("number" === typeof j &&
|
||||
j === +j &&
|
||||
j > 0) {
|
||||
// XXX Better, faster way to get BigInteger from JS int?
|
||||
this._value = new BigInteger(""+j);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Convert from internal form.
|
||||
UInt.prototype.to_bytes = function () {
|
||||
if (!(this._value instanceof BigInteger))
|
||||
|
||||
@@ -19,8 +19,8 @@ describe('Currency', function() {
|
||||
});
|
||||
it('from_json("XRP").to_json() == "XRP"', function() {
|
||||
var r = currency.from_json('XRP');
|
||||
assert.strictEqual(0, r._value);
|
||||
assert(r.is_valid());
|
||||
assert(r.is_native());
|
||||
assert.strictEqual('XRP', r.to_json());
|
||||
});
|
||||
});
|
||||
|
||||
@@ -531,6 +531,16 @@ describe('Serialized types', function() {
|
||||
});
|
||||
assert.strictEqual(so.to_hex(), 'D5438D7EA4C680000000000000000000000000005852500000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367');
|
||||
});
|
||||
// Test support for 20-byte hex raw currency codes
|
||||
it('Serialize 15/015841551A748AD23FEFFFFFFFEA028000000000/1', function () {
|
||||
var so = new SerializedObject();
|
||||
types.Amount.serialize(so, {
|
||||
"value":"1000",
|
||||
"currency":"015841551A748AD23FEFFFFFFFEA028000000000",
|
||||
"issuer":"rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo"
|
||||
});
|
||||
assert.strictEqual(so.to_hex(), 'D5438D7EA4C68000015841551A748AD23FEFFFFFFFEA028000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367');
|
||||
});
|
||||
it('Parse 1 XRP', function () {
|
||||
var so = new SerializedObject('4000000000000001');
|
||||
assert.strictEqual(types.Amount.parse(so).to_json(), '1');
|
||||
|
||||
Reference in New Issue
Block a user