Skip to content

Commit 3f6c143

Browse files
committed
Migrate code to BigInt where possible
1 parent afaacb5 commit 3f6c143

11 files changed

+79
-53
lines changed

config/.eslintrc.js

+2-1
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

+2-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"keywords": [
6363
"stellar"
6464
],
65-
"author": "George Kudrayvtsev <george@stellar.org>",
65+
"author": "Stellar Development Foundation <hello@stellar.org>",
6666
"license": "Apache-2.0",
6767
"bugs": {
6868
"url": "https://github.com/stellar/js-stellar-base/issues"
@@ -84,6 +84,7 @@
8484
"buffer": "^6.0.3",
8585
"chai": "^4.3.7",
8686
"cross-env": "^7.0.3",
87+
"crypto-browserify": "^3.12.0",
8788
"eslint": "^8.37.0",
8889
"eslint-config-airbnb-base": "^15.0.0",
8990
"eslint-config-prettier": "^8.8.0",

src/account.js

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import BigNumber from 'bignumber.js';
2-
31
import { StrKey } from './strkey';
42

53
/**
@@ -33,7 +31,11 @@ export class Account {
3331
}
3432

3533
this._accountId = accountId;
36-
this.sequence = new BigNumber(sequence);
34+
try {
35+
this.sequence = BigInt(sequence);
36+
} catch (e) {
37+
throw new Error(`sequence is not a number: ${sequence}`);
38+
}
3739
}
3840

3941
/**
@@ -57,6 +59,6 @@ export class Account {
5759
* @returns {void}
5860
*/
5961
incrementSequenceNumber() {
60-
this.sequence = this.sequence.plus(1);
62+
this.sequence += 1n;
6163
}
6264
}

src/index.js

-3
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

+1-13
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 xdr from './xdr';
43

54
/**
@@ -102,22 +101,11 @@ export class Memo {
102101
throw error;
103102
}
104103

105-
let number;
106104
try {
107-
number = new BigNumber(value);
105+
BigInt(value); // throws on invalid, inf, or NaN
108106
} catch (e) {
109107
throw error;
110108
}
111-
112-
// Infinity
113-
if (!number.isFinite()) {
114-
throw error;
115-
}
116-
117-
// NaN
118-
if (number.isNaN()) {
119-
throw error;
120-
}
121109
}
122110

123111
static _validateTextValue(value) {

src/operation.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import {
1919
const ONE = 10000000;
2020
const MAX_INT64 = '9223372036854775807';
2121

22+
BigNumber.DEBUG = true; // gives us exceptions on bad constructor values
23+
2224
/**
2325
* When set using `{@link Operation.setOptions}` option, requires the issuing
2426
* account to give other accounts permission before they can hold the issuing
@@ -502,10 +504,10 @@ export class Operation {
502504
if (price.n && price.d) {
503505
xdrObject = new xdr.Price(price);
504506
} else {
505-
const approx = best_r(price);
507+
const [n, d] = best_r(price);
506508
xdrObject = new xdr.Price({
507-
n: parseInt(approx[0], 10),
508-
d: parseInt(approx[1], 10)
509+
n: parseInt(n, 10),
510+
d: parseInt(d, 10)
509511
});
510512
}
511513

src/operations/bump_sequence.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Hyper } from 'js-xdr';
2-
import BigNumber from 'bignumber.js';
2+
33
import xdr from '../xdr';
44

55
/**
@@ -19,8 +19,7 @@ export function bumpSequence(opts) {
1919
}
2020

2121
try {
22-
// eslint-disable-next-line no-new
23-
new BigNumber(opts.bumpTo);
22+
BigInt(opts.bumpTo);
2423
} catch (e) {
2524
throw new Error('bumpTo must be a stringified number');
2625
}

src/operations/change_trust.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { Hyper } from 'js-xdr';
2-
import BigNumber from 'bignumber.js';
32
import xdr from '../xdr';
43
import { Asset } from '../asset';
54
import { LiquidityPoolAsset } from '../liquidity_pool_asset';
65

7-
const MAX_INT64 = '9223372036854775807';
6+
const MAX_INT64 = 9223372036854775807n;
87

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

4241
if (opts.source) {

src/transaction_builder.js

+13-14
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

43
import xdr from './xdr';
54
import { Transaction } from './transaction';
@@ -440,10 +439,10 @@ export class TransactionBuilder {
440439
* @returns {Transaction} This method will return the built {@link Transaction}.
441440
*/
442441
build() {
443-
const sequenceNumber = new BigNumber(this.source.sequenceNumber()).plus(1);
444-
const fee = new BigNumber(this.baseFee)
445-
.times(this.operations.length)
446-
.toNumber();
442+
const sequenceNumber = BigInt(this.source.sequenceNumber()) + 1n;
443+
const fee = Number(
444+
BigInt.asIntN(32, BigInt(this.baseFee) * BigInt(this.operations.length))
445+
); // base tx is int32 in XDR, feebump is int64
447446
const attrs = {
448447
fee,
449448
seqNum: xdr.SequenceNumber.fromString(sequenceNumber.toString()),
@@ -568,23 +567,23 @@ export class TransactionBuilder {
568567
innerTx,
569568
networkPassphrase
570569
) {
571-
const innerOps = innerTx.operations.length;
572-
const innerBaseFeeRate = new BigNumber(innerTx.fee).div(innerOps);
573-
const base = new BigNumber(baseFee);
570+
const innerOps = BigInt(innerTx.operations.length);
571+
const innerBaseFeeRate = BigInt(innerTx.fee) / innerOps; // truncates
572+
const base = BigInt(baseFee);
574573

575574
// The fee rate for fee bump is at least the fee rate of the inner transaction
576-
if (base.lt(innerBaseFeeRate)) {
575+
if (base < innerBaseFeeRate) {
577576
throw new Error(
578-
`Invalid baseFee, it should be at least ${innerBaseFeeRate} stroops.`
577+
`Invalid baseFee (${baseFee}), it should be at least ${innerBaseFeeRate} stroops.`
579578
);
580579
}
581580

582-
const minBaseFee = new BigNumber(BASE_FEE);
581+
const minBaseFee = BigInt(BASE_FEE);
583582

584583
// The fee rate is at least the minimum fee
585-
if (base.lt(minBaseFee)) {
584+
if (base < minBaseFee) {
586585
throw new Error(
587-
`Invalid baseFee, it should be at least ${minBaseFee} stroops.`
586+
`Invalid baseFee (${baseFee}), it should be at least ${minBaseFee} stroops.`
588587
);
589588
}
590589

@@ -619,7 +618,7 @@ export class TransactionBuilder {
619618

620619
const tx = new xdr.FeeBumpTransaction({
621620
feeSource: feeSourceAccount,
622-
fee: xdr.Int64.fromString(base.times(innerOps + 1).toString()),
621+
fee: xdr.Int64.fromString(BigInt.asIntN(64, (base * (innerOps + 1n)).toString())),
623622
innerTx: xdr.FeeBumpTransactionInnerTx.envelopeTypeTx(
624623
innerTxEnvelope.v1()
625624
),

src/util/continued_fraction.js

+44-6
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
}

test/unit/transaction_builder_test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -528,7 +528,7 @@ describe('TransactionBuilder', function () {
528528
innerTx,
529529
networkPassphrase
530530
);
531-
}).to.throw(/Invalid baseFee, it should be at least 200 stroops./);
531+
}).to.throw(/it should be at least 200 stroops/i);
532532

533533
innerTx = new StellarBase.TransactionBuilder(innerAccount, {
534534
fee: '80',
@@ -556,7 +556,7 @@ describe('TransactionBuilder', function () {
556556
innerTx,
557557
networkPassphrase
558558
);
559-
}).to.throw(/Invalid baseFee, it should be at least 100 stroops./);
559+
}).to.throw(/it should be at least 100 stroops/i);
560560

561561
innerTx = new StellarBase.TransactionBuilder(innerAccount, {
562562
fee: '100',

0 commit comments

Comments
 (0)