Skip to content

Commit e30fc92

Browse files
committed
Migrate code to BigInt where possible
1 parent c4ff6ed commit e30fc92

File tree

12 files changed

+86
-55
lines changed

12 files changed

+86
-55
lines changed

config/.eslintrc.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
env: {
3-
es6: true
3+
es6: true,
4+
es2020: true
45
},
56
extends: ['airbnb-base', 'prettier'],
67
plugins: ['@babel', 'prettier', 'prefer-import'],

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"keywords": [
6262
"stellar"
6363
],
64-
"author": "George Kudrayvtsev <george@stellar.org>",
64+
"author": "Stellar Development Foundation <hello@stellar.org>",
6565
"license": "Apache-2.0",
6666
"bugs": {
6767
"url": "https://github.com/stellar/js-stellar-base/issues"
@@ -83,6 +83,7 @@
8383
"buffer": "^6.0.3",
8484
"chai": "^4.3.7",
8585
"cross-env": "^7.0.3",
86+
"crypto-browserify": "^3.12.0",
8687
"eslint": "^8.37.0",
8788
"eslint-config-airbnb-base": "^15.0.0",
8889
"eslint-config-prettier": "^8.8.0",
@@ -121,7 +122,6 @@
121122
"base32.js": "^0.1.0",
122123
"bignumber.js": "^9.1.1",
123124
"crc": "^4.3.2",
124-
"crypto-browserify": "^3.12.0",
125125
"js-xdr": "^2.0.0",
126126
"lodash": "^4.17.21",
127127
"sha.js": "^2.3.6",

src/account.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import isString from 'lodash/isString';
2-
import BigNumber from 'bignumber.js';
32

43
import { StrKey } from './strkey';
54

@@ -34,7 +33,11 @@ export class Account {
3433
}
3534

3635
this._accountId = accountId;
37-
this.sequence = new BigNumber(sequence);
36+
try {
37+
this.sequence = BigInt(sequence);
38+
} catch (e) {
39+
throw new Error(`sequence is not a number: ${sequence}`);
40+
}
3841
}
3942

4043
/**
@@ -58,6 +61,6 @@ export class Account {
5861
* @returns {void}
5962
*/
6063
incrementSequenceNumber() {
61-
this.sequence = this.sequence.plus(1);
64+
this.sequence += 1n;
6265
}
6366
}

src/index.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
/* eslint-disable import/no-import-module-exports */
2-
import BigNumber from 'bignumber.js';
32
import xdr from './xdr';
43

5-
BigNumber.DEBUG = true; // gives us exceptions on bad constructor values
6-
74
export { xdr };
85
export { hash } from './hashing';
96
export { sign, verify, FastSigning } from './signing';

src/memo.js

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import isUndefined from 'lodash/isUndefined';
22
import isString from 'lodash/isString';
33
import clone from 'lodash/clone';
44
import { UnsignedHyper } from 'js-xdr';
5-
import BigNumber from 'bignumber.js';
65
import xdr from './xdr';
76

87
/**
@@ -105,22 +104,11 @@ export class Memo {
105104
throw error;
106105
}
107106

108-
let number;
109107
try {
110-
number = new BigNumber(value);
108+
BigInt(value); // throws on invalid, inf, or NaN
111109
} catch (e) {
112110
throw error;
113111
}
114-
115-
// Infinity
116-
if (!number.isFinite()) {
117-
throw error;
118-
}
119-
120-
// NaN
121-
if (number.isNaN()) {
122-
throw error;
123-
}
124112
}
125113

126114
static _validateTextValue(value) {

src/operation.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import {
2323
const ONE = 10000000;
2424
const MAX_INT64 = '9223372036854775807';
2525

26+
BigNumber.DEBUG = true; // gives us exceptions on bad constructor values
27+
2628
/**
2729
* When set using `{@link Operation.setOptions}` option, requires the issuing
2830
* account to give other accounts permission before they can hold the issuing
@@ -489,10 +491,10 @@ export class Operation {
489491
if (price.n && price.d) {
490492
xdrObject = new xdr.Price(price);
491493
} else {
492-
const approx = best_r(price);
494+
const [n, d] = best_r(price);
493495
xdrObject = new xdr.Price({
494-
n: parseInt(approx[0], 10),
495-
d: parseInt(approx[1], 10)
496+
n: parseInt(n, 10),
497+
d: parseInt(d, 10)
496498
});
497499
}
498500

src/operations/bump_sequence.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { Hyper } from 'js-xdr';
2-
import BigNumber from 'bignumber.js';
32
import isString from 'lodash/isString';
43
import xdr from '../xdr';
54

@@ -20,8 +19,7 @@ export function bumpSequence(opts) {
2019
}
2120

2221
try {
23-
// eslint-disable-next-line no-new
24-
new BigNumber(opts.bumpTo);
22+
BigInt(opts.bumpTo);
2523
} catch (e) {
2624
throw new Error('bumpTo must be a stringified number');
2725
}

src/operations/change_trust.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import isUndefined from 'lodash/isUndefined';
22
import { Hyper } from 'js-xdr';
3-
import BigNumber from 'bignumber.js';
43
import xdr from '../xdr';
54
import { Asset } from '../asset';
65
import { LiquidityPoolAsset } from '../liquidity_pool_asset';
76

8-
const MAX_INT64 = '9223372036854775807';
7+
const MAX_INT64 = 9223372036854775807n;
98

109
/**
1110
* Returns an XDR ChangeTrustOp. A "change trust" operation adds, removes, or updates a
@@ -37,7 +36,7 @@ export function changeTrust(opts) {
3736
if (opts.limit) {
3837
attributes.limit = this._toXDRAmount(opts.limit);
3938
} else {
40-
attributes.limit = Hyper.fromString(new BigNumber(MAX_INT64).toString());
39+
attributes.limit = Hyper.fromString(MAX_INT64.toString());
4140
}
4241

4342
if (opts.source) {

src/transaction_builder.js

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { UnsignedHyper } from 'js-xdr';
2-
import BigNumber from 'bignumber.js';
32
import clone from 'lodash/clone';
43
import isUndefined from 'lodash/isUndefined';
54
import isString from 'lodash/isString';
@@ -444,10 +443,10 @@ export class TransactionBuilder {
444443
* @returns {Transaction} This method will return the built {@link Transaction}.
445444
*/
446445
build() {
447-
const sequenceNumber = new BigNumber(this.source.sequenceNumber()).plus(1);
448-
const fee = new BigNumber(this.baseFee)
449-
.times(this.operations.length)
450-
.toNumber();
446+
const sequenceNumber = BigInt(this.source.sequenceNumber()) + 1n;
447+
const fee = Number(
448+
BigInt.asIntN(32, BigInt(this.baseFee) * BigInt(this.operations.length))
449+
); // base tx is int32 in XDR, feebump is int64
451450
const attrs = {
452451
fee,
453452
seqNum: xdr.SequenceNumber.fromString(sequenceNumber.toString()),
@@ -572,23 +571,23 @@ export class TransactionBuilder {
572571
innerTx,
573572
networkPassphrase
574573
) {
575-
const innerOps = innerTx.operations.length;
576-
const innerBaseFeeRate = new BigNumber(innerTx.fee).div(innerOps);
577-
const base = new BigNumber(baseFee);
574+
const innerOps = BigInt(innerTx.operations.length);
575+
const innerBaseFeeRate = BigInt(innerTx.fee) / innerOps; // truncates
576+
const base = BigInt(baseFee);
578577

579578
// The fee rate for fee bump is at least the fee rate of the inner transaction
580-
if (base.lt(innerBaseFeeRate)) {
579+
if (base < innerBaseFeeRate) {
581580
throw new Error(
582-
`Invalid baseFee, it should be at least ${innerBaseFeeRate} stroops.`
581+
`Invalid baseFee (${baseFee}), it should be at least ${innerBaseFeeRate} stroops.`
583582
);
584583
}
585584

586-
const minBaseFee = new BigNumber(BASE_FEE);
585+
const minBaseFee = BigInt(BASE_FEE);
587586

588587
// The fee rate is at least the minimum fee
589-
if (base.lt(minBaseFee)) {
588+
if (base < minBaseFee) {
590589
throw new Error(
591-
`Invalid baseFee, it should be at least ${minBaseFee} stroops.`
590+
`Invalid baseFee (${baseFee}), it should be at least ${minBaseFee} stroops.`
592591
);
593592
}
594593

@@ -623,7 +622,7 @@ export class TransactionBuilder {
623622

624623
const tx = new xdr.FeeBumpTransaction({
625624
feeSource: feeSourceAccount,
626-
fee: xdr.Int64.fromString(base.times(innerOps + 1).toString()),
625+
fee: xdr.Int64.fromString(BigInt.asIntN(64, (base * (innerOps + 1n)).toString())),
627626
innerTx: xdr.FeeBumpTransactionInnerTx.envelopeTypeTx(
628627
innerTxEnvelope.v1()
629628
),

src/util/continued_fraction.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,62 @@ import BigNumber from 'bignumber.js';
44
const MAX_INT = ((1 << 31) >>> 0) - 1;
55

66
/**
7-
* Calculates and returns the best rational approximation of the given real number.
7+
* Calculates and returns the best rational (fractional) approximation of the
8+
* given real number.
9+
*
810
* @private
9-
* @param {string|number|BigNumber} rawNumber Real number
10-
* @throws Error Throws `Error` when the best rational approximation cannot be found.
11-
* @returns {array} first element is n (numerator), second element is d (denominator)
11+
*
12+
* This is used internally to convert real-number-like prices into fractions for
13+
* XDR to use as part of DEX offer & LP management.
14+
*
15+
* @param {string|number|BigInt} rawNumber the "real" number to approximate
16+
*
17+
* @returns {number[]} the numerator and denominator of the fractional
18+
* approximation, respectively, where neither value exceeds `MAX_INT32`
19+
*
20+
* @throws {Error} throws an `Error` when no good rational approximation can be
21+
* found.
1222
*/
1323
export function best_r(rawNumber) {
14-
let number = new BigNumber(rawNumber);
24+
BigNumber.DEBUG = true; // gives us exceptions on bad constructor values
25+
26+
// NOTE: We can't convert this to use BigInt because the rational component is
27+
// crucial to calculating the approximation.
28+
let number = BigNumber(rawNumber);
1529
let a;
1630
let f;
31+
32+
// We start with 0/1 and 1/0 as our approximations (the latter is technically
33+
// undefined but we need it as a starting point)
1734
const fractions = [
1835
[new BigNumber(0), new BigNumber(1)],
1936
[new BigNumber(1), new BigNumber(0)]
2037
];
2138
let i = 2;
2239

40+
/*
41+
The algorithm is a form of the continued fraction expansion (hinted at by the
42+
filename):
43+
44+
> A continued fraction is an expression obtained through an iterative process
45+
> of representing a number as the sum of its integer part and the reciprocal
46+
> of another number, then writing this other number as the sum of its integer
47+
> part and another reciprocal, and so on.
48+
49+
https://en.wikipedia.org/wiki/Continued_fraction
50+
51+
We run this loop until either:
52+
53+
- any part of the fraction exceeds MAX_INT (though JS can handle bigger
54+
numbers just fine, the xdr.Price object uses int32 values), OR
55+
56+
- the "remainder" (`f` in the below loop) is zero (this means we've gotten a
57+
perfect approximation)
58+
*/
2359
// eslint-disable-next-line no-constant-condition
2460
while (true) {
61+
// Compare the delta between the rational `number` and its truncated integer
62+
// equivalent: `f` is everything after the decimal point.
2563
if (number.gt(MAX_INT)) {
2664
break;
2765
}
@@ -39,8 +77,8 @@ export function best_r(rawNumber) {
3977
number = new BigNumber(1).div(f);
4078
i += 1;
4179
}
42-
const [n, d] = fractions[fractions.length - 1];
4380

81+
const [n, d] = fractions[fractions.length - 1];
4482
if (n.isZero() || d.isZero()) {
4583
throw new Error("Couldn't find approximation");
4684
}

0 commit comments

Comments
 (0)