mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 20:25:48 +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) {
|
switch (typeof j) {
|
||||||
case 'string':
|
case 'string':
|
||||||
// .../.../... notation is not a wire format. But allowed for easier testing.
|
// .../.../... notation is not a wire format. But allowed for easier testing.
|
||||||
var m = j.match(/^([^/]+)\/(...)(?:\/(.+))?$/);
|
var m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||||
|
|
||||||
if (m) {
|
if (m) {
|
||||||
this._currency = Currency.from_json(m[2]);
|
this._currency = Currency.from_json(m[2]);
|
||||||
@@ -655,7 +655,7 @@ Amount.prototype.parse_json = function (j) {
|
|||||||
j.copyTo(this);
|
j.copyTo(this);
|
||||||
} else if (j.hasOwnProperty('value')) {
|
} else if (j.hasOwnProperty('value')) {
|
||||||
// Parse the passed value to sanitize and copy it.
|
// 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') {
|
if (typeof j.issuer === 'string') {
|
||||||
this._issuer.parse_json(j.issuer);
|
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
|
// Currency support
|
||||||
//
|
//
|
||||||
|
|
||||||
// XXX Internal form should be UInt160.
|
var Currency = extend(function () {
|
||||||
function Currency() {
|
|
||||||
// Internal form: 0 = XRP. 3 letter-code.
|
// Internal form: 0 = XRP. 3 letter-code.
|
||||||
// XXX Internal should be 0 or hex with three letter annotation when valid.
|
// 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.
|
// XXX Should support hex, C++ doesn't currently allow it.
|
||||||
|
|
||||||
this._value = NaN;
|
this._value = NaN;
|
||||||
};
|
}, UInt160);
|
||||||
|
|
||||||
// Given "USD" return the json.
|
Currency.prototype = extend({}, UInt160.prototype);
|
||||||
Currency.json_rewrite = function (j) {
|
Currency.prototype.constructor = Currency;
|
||||||
return Currency.from_json(j).to_json();
|
|
||||||
};
|
|
||||||
|
|
||||||
Currency.from_json = function (j) {
|
Currency.HEX_CURRENCY_BAD = "0000000000000000000000005852500000000000";
|
||||||
return j instanceof Currency ? j.clone() : new Currency().parse_json(j);
|
|
||||||
};
|
|
||||||
|
|
||||||
Currency.from_bytes = function (j) {
|
Currency.from_json = function (j, shouldInterpretXrpAsIou) {
|
||||||
return j instanceof Currency ? j.clone() : new Currency().parse_bytes(j);
|
if (j instanceof this) {
|
||||||
};
|
return j.clone();
|
||||||
|
} else {
|
||||||
Currency.is_valid = function (j) {
|
return (new this()).parse_json(j, shouldInterpretXrpAsIou);
|
||||||
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;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// this._value = NaN on error.
|
// this._value = NaN on error.
|
||||||
Currency.prototype.parse_json = function (j) {
|
Currency.prototype.parse_json = function (j, shouldInterpretXrpAsIou) {
|
||||||
var result = NaN;
|
this._value = NaN;
|
||||||
|
|
||||||
switch (typeof j) {
|
switch (typeof j) {
|
||||||
case 'string':
|
case 'string':
|
||||||
if (!j || /^(0|XRP)$/.test(j)) {
|
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)) {
|
} 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;
|
break;
|
||||||
|
|
||||||
case 'number':
|
case 'number':
|
||||||
if (!isNaN(j)) {
|
if (!isNaN(j)) {
|
||||||
result = j;
|
this.parse_number(j);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'object':
|
case 'object':
|
||||||
if (j instanceof Currency) {
|
if (j instanceof Currency) {
|
||||||
result = j.copyTo({})._value;
|
this._value = j.copyTo({})._value;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._value = result;
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// XXX Probably not needed anymore?
|
||||||
|
/*
|
||||||
Currency.prototype.parse_bytes = function (byte_array) {
|
Currency.prototype.parse_bytes = function (byte_array) {
|
||||||
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
if (Array.isArray(byte_array) && byte_array.length === 20) {
|
||||||
var result;
|
var result;
|
||||||
@@ -110,21 +105,62 @@ Currency.prototype.parse_bytes = function (byte_array) {
|
|||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
*/
|
||||||
|
|
||||||
Currency.prototype.is_native = function () {
|
Currency.prototype.is_native = function () {
|
||||||
return !isNaN(this._value) && !this._value;
|
return !isNaN(this._value) && this.is_zero();
|
||||||
};
|
};
|
||||||
|
|
||||||
Currency.prototype.is_valid = function () {
|
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||||
return typeof this._value === 'string' || !isNaN(this._value);
|
//
|
||||||
};
|
// 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 () {
|
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 () {
|
Currency.prototype.to_human = function () {
|
||||||
return this._value ? this._value : "XRP";
|
return this.to_json();
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.Currency = Currency;
|
exports.Currency = Currency;
|
||||||
|
|||||||
@@ -280,22 +280,13 @@ STHash160.id = 17;
|
|||||||
// Internal
|
// Internal
|
||||||
var STCurrency = new SerializedType({
|
var STCurrency = new SerializedType({
|
||||||
serialize: function (so, val, xrp_as_ascii) {
|
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.');
|
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);
|
so.append(currencyData);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
parse: function (so) {
|
parse: function (so) {
|
||||||
var bytes = so.read(20);
|
var bytes = so.read(20);
|
||||||
@@ -484,8 +475,8 @@ var STPathSet = exports.PathSet = new SerializedType({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (entry.currency) {
|
if (entry.currency) {
|
||||||
var currency = Currency.from_json(entry.currency);
|
var currency = Currency.from_json(entry.currency, entry.non_native);
|
||||||
STCurrency.serialize(so, currency, entry.non_native);
|
STCurrency.serialize(so, currency);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.issuer) {
|
if (entry.issuer) {
|
||||||
@@ -544,7 +535,8 @@ var STPathSet = exports.PathSet = new SerializedType({
|
|||||||
}
|
}
|
||||||
if (tag_byte & this.typeIssuer) {
|
if (tag_byte & this.typeIssuer) {
|
||||||
//console.log('entry.issuer');
|
//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);
|
entry.issuer.set_version(Base.VER_ACCOUNT_ID);
|
||||||
//console.log('DONE WITH ISSUER!');
|
//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) {
|
UInt.is_valid = function (j) {
|
||||||
return this.from_json(j).is_valid();
|
return this.from_json(j).is_valid();
|
||||||
};
|
};
|
||||||
@@ -192,6 +201,19 @@ UInt.prototype.parse_bn = function (j) {
|
|||||||
return this;
|
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.
|
// Convert from internal form.
|
||||||
UInt.prototype.to_bytes = function () {
|
UInt.prototype.to_bytes = function () {
|
||||||
if (!(this._value instanceof BigInteger))
|
if (!(this._value instanceof BigInteger))
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ describe('Currency', function() {
|
|||||||
});
|
});
|
||||||
it('from_json("XRP").to_json() == "XRP"', function() {
|
it('from_json("XRP").to_json() == "XRP"', function() {
|
||||||
var r = currency.from_json('XRP');
|
var r = currency.from_json('XRP');
|
||||||
assert.strictEqual(0, r._value);
|
|
||||||
assert(r.is_valid());
|
assert(r.is_valid());
|
||||||
|
assert(r.is_native());
|
||||||
assert.strictEqual('XRP', r.to_json());
|
assert.strictEqual('XRP', r.to_json());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -531,6 +531,16 @@ describe('Serialized types', function() {
|
|||||||
});
|
});
|
||||||
assert.strictEqual(so.to_hex(), 'D5438D7EA4C680000000000000000000000000005852500000000000E4FE687C90257D3D2D694C8531CDEECBE84F3367');
|
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 () {
|
it('Parse 1 XRP', function () {
|
||||||
var so = new SerializedObject('4000000000000001');
|
var so = new SerializedObject('4000000000000001');
|
||||||
assert.strictEqual(types.Amount.parse(so).to_json(), '1');
|
assert.strictEqual(types.Amount.parse(so).to_json(), '1');
|
||||||
|
|||||||
Reference in New Issue
Block a user