Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace bn.js with BigInt #1223

Merged
merged 11 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .changeset/eight-owls-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"near-api-js": major
"@near-js/accounts": minor
"@near-js/transactions": minor
"@near-js/types": minor
"@near-js/utils": minor
"@near-js/biometric-ed25519": patch
"@near-js/cookbook": patch
"@near-js/crypto": patch
"@near-js/providers": patch
"@near-js/wallet-account": patch
---

Replace bn.js by BigInt.
1 change: 1 addition & 0 deletions .eslintrc.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ globals:
jasmine: true
window: false
fail: true
BigInt: true
1 change: 0 additions & 1 deletion packages/accounts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"@near-js/utils": "workspace:*",
"ajv": "8.11.2",
"ajv-formats": "2.1.1",
"bn.js": "5.2.1",
"borsh": "1.0.0",
"depd": "2.0.0",
"lru_map": "0.4.1",
Expand Down
49 changes: 24 additions & 25 deletions packages/accounts/src/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
printTxOutcomeLogs,
printTxOutcomeLogsAndFailures,
} from '@near-js/utils';
import BN from 'bn.js';

import { Connection } from './connection';

Expand Down Expand Up @@ -106,9 +105,9 @@ export interface FunctionCallOptions {
*/
args?: object;
/** max amount of gas that method call can use */
gas?: BN;
gas?: bigint;
/** amount of NEAR (in yoctoNEAR) to send together with the call */
attachedDeposit?: BN;
attachedDeposit?: bigint;
/**
* Convert input arguments into bytes array.
*/
Expand Down Expand Up @@ -145,7 +144,7 @@ interface StakedBalance {
interface ActiveDelegatedStakeBalance {
stakedValidators: StakedBalance[];
failedValidators: StakedBalance[];
total: BN | string;
total: bigint | string;
}

interface SignedDelegateOptions {
Expand Down Expand Up @@ -202,7 +201,7 @@ export class Account {
const block = await this.connection.provider.block({ finality: 'final' });
const blockHash = block.header.hash;

const nonce = accessKey.nonce.add(new BN(1));
const nonce = accessKey.nonce + BigInt(1);
return await signTransaction(
receiverId, nonce, actions, baseDecode(blockHash), this.connection.signer, this.accountId, this.connection.networkId
);
Expand Down Expand Up @@ -291,10 +290,10 @@ export class Account {
finality: 'optimistic'
});

// store nonce as BN to preserve precision on big number
// store nonce as BigInt to preserve precision on big number
const accessKey = {
...rawAccessKey,
nonce: new BN(rawAccessKey.nonce),
nonce: BigInt(rawAccessKey.nonce || 0)
gagdiez marked this conversation as resolved.
Show resolved Hide resolved
};
// this function can be called multiple times and retrieve the same access key
// this checks to see if the access key was already retrieved and cached while
Expand Down Expand Up @@ -323,7 +322,7 @@ export class Account {
* @param data The compiled contract code
* @param amount of NEAR to transfer to the created contract account. Transfer enough to pay for storage https://docs.near.org/docs/concepts/storage-staking
*/
async createAndDeployContract(contractId: string, publicKey: string | PublicKey, data: Uint8Array, amount: BN): Promise<Account> {
async createAndDeployContract(contractId: string, publicKey: string | PublicKey, data: Uint8Array, amount: bigint): Promise<Account> {
const accessKey = fullAccessKey();
await this.signAndSendTransaction({
receiverId: contractId,
Expand All @@ -337,7 +336,7 @@ export class Account {
* @param receiverId NEAR account receiving Ⓝ
* @param amount Amount to send in yoctoⓃ
*/
async sendMoney(receiverId: string, amount: BN): Promise<FinalExecutionOutcome> {
async sendMoney(receiverId: string, amount: bigint): Promise<FinalExecutionOutcome> {
return this.signAndSendTransaction({
receiverId,
actions: [transfer(amount)]
Expand All @@ -348,7 +347,7 @@ export class Account {
* @param newAccountId NEAR account name to be created
* @param publicKey A public key created from the masterAccount
*/
async createAccount(newAccountId: string, publicKey: string | PublicKey, amount: BN): Promise<FinalExecutionOutcome> {
async createAccount(newAccountId: string, publicKey: string | PublicKey, amount: bigint): Promise<FinalExecutionOutcome> {
const accessKey = fullAccessKey();
return this.signAndSendTransaction({
receiverId: newAccountId,
Expand Down Expand Up @@ -415,7 +414,7 @@ export class Account {
* @param methodNames The method names on the contract that should be allowed to be called. Pass null for no method names and '' or [] for any method names.
* @param amount Payment in yoctoⓃ that is sent to the contract during this function call
*/
async addKey(publicKey: string | PublicKey, contractId?: string, methodNames?: string | string[], amount?: BN): Promise<FinalExecutionOutcome> {
async addKey(publicKey: string | PublicKey, contractId?: string, methodNames?: string | string[], amount?: bigint): Promise<FinalExecutionOutcome> {
if (!methodNames) {
methodNames = [];
}
Expand Down Expand Up @@ -451,7 +450,7 @@ export class Account {
* @param publicKey The public key for the account that's staking
* @param amount The account to stake in yoctoⓃ
*/
async stake(publicKey: string | PublicKey, amount: BN): Promise<FinalExecutionOutcome> {
async stake(publicKey: string | PublicKey, amount: bigint): Promise<FinalExecutionOutcome> {
return this.signAndSendTransaction({
receiverId: this.accountId,
actions: [stake(amount, PublicKey.from(publicKey))]
Expand All @@ -476,8 +475,8 @@ export class Account {

const delegateAction = buildDelegateAction({
actions,
maxBlockHeight: new BN(header.height).add(new BN(blockHeightTtl)),
nonce: new BN(accessKey.nonce).add(new BN(1)),
maxBlockHeight: BigInt(header.height) + BigInt(blockHeightTtl),
nonce: BigInt(accessKey.nonce) + BigInt(1),
publicKey,
receiverId,
senderId: this.accountId,
Expand Down Expand Up @@ -593,8 +592,8 @@ export class Account {
account_id: this.accountId,
finality: 'optimistic'
});
// Replace raw nonce into a new BN
return response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: new BN(key.access_key.nonce) } }));
// Replace raw nonce into a new BigInt
return response?.keys?.map((key) => ({ ...key, access_key: { ...key.access_key, nonce: BigInt(key.access_key.nonce) } }));
}

/**
Expand Down Expand Up @@ -625,11 +624,11 @@ export class Account {
const protocolConfig = await this.connection.provider.experimental_protocolConfig({ finality: 'final' });
const state = await this.state();

const costPerByte = new BN(protocolConfig.runtime_config.storage_amount_per_byte);
const stateStaked = new BN(state.storage_usage).mul(costPerByte);
const staked = new BN(state.locked);
const totalBalance = new BN(state.amount).add(staked);
const availableBalance = totalBalance.sub(BN.max(staked, stateStaked));
const costPerByte = BigInt(protocolConfig.runtime_config.storage_amount_per_byte);
const stateStaked = BigInt(state.storage_usage) * costPerByte;
const staked = BigInt(state.locked);
const totalBalance = BigInt(state.amount) + staked;
const availableBalance = totalBalance - (staked > stateStaked ? staked : stateStaked);

return {
total: totalBalance.toString(),
Expand Down Expand Up @@ -681,12 +680,12 @@ export class Account {
const summary = results.reduce((result, state, index) => {
const validatorId = uniquePools[index];
if (state.status === 'fulfilled') {
const currentBN = new BN(state.value);
if (!currentBN.isZero()) {
const currentBN = BigInt(state.value);
if (currentBN !== BigInt(0)) {
return {
...result,
stakedValidators: [...result.stakedValidators, { validatorId, amount: currentBN.toString() }],
total: result.total.add(currentBN),
total: result.total + currentBN,
};
}
}
Expand All @@ -698,7 +697,7 @@ export class Account {
}
return result;
},
{ stakedValidators: [], failedValidators: [], total: new BN(0) });
{ stakedValidators: [], failedValidators: [], total: BigInt(0) });

return {
...summary,
Expand Down
3 changes: 1 addition & 2 deletions packages/accounts/src/account_2fa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { FinalExecutionOutcome, TypedError, FunctionCallPermissionView } from '@
import { fetchJson } from '@near-js/providers';
import { actionCreators } from '@near-js/transactions';
import { Logger } from '@near-js/utils'
import BN from 'bn.js';

import { SignAndSendTransactionOptions } from './account';
import { AccountMultisig } from './account_multisig';
Expand Down Expand Up @@ -137,7 +136,7 @@ export class Account2FA extends AccountMultisig {
const currentAccountStateKeys = currentAccountState.map(({ key }) => key.toString('base64'));
return currentAccountState.length ? [
deployContract(cleanupContractBytes),
functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, new BN('0'))
functionCall('clean', { keys: currentAccountStateKeys }, MULTISIG_GAS, BigInt('0'))
] : [];
}

Expand Down
5 changes: 2 additions & 3 deletions packages/accounts/src/account_creator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { PublicKey } from '@near-js/crypto';
import { fetchJson } from '@near-js/providers';
import BN from 'bn.js';

import { Connection } from './connection';
import { Account } from './account';
Expand All @@ -14,9 +13,9 @@ export abstract class AccountCreator {

export class LocalAccountCreator extends AccountCreator {
readonly masterAccount: Account;
readonly initialBalance: BN;
readonly initialBalance: bigint;

constructor(masterAccount: Account, initialBalance: BN) {
constructor(masterAccount: Account, initialBalance: bigint) {
super();
this.masterAccount = masterAccount;
this.initialBalance = initialBalance;
Expand Down
7 changes: 3 additions & 4 deletions packages/accounts/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { parseNearAmount } from '@near-js/utils';
import BN from 'bn.js';

export const MULTISIG_STORAGE_KEY = '__multisigRequest';
export const MULTISIG_ALLOWANCE = new BN(parseNearAmount('1'));
export const MULTISIG_ALLOWANCE = BigInt(parseNearAmount('1'));
// TODO: Different gas value for different requests (can reduce gas usage dramatically)
export const MULTISIG_GAS = new BN('100000000000000');
export const MULTISIG_DEPOSIT = new BN('0');
export const MULTISIG_GAS = BigInt('100000000000000');
export const MULTISIG_DEPOSIT = BigInt('0');
export const MULTISIG_CHANGE_METHODS = ['add_request', 'add_request_and_confirm', 'delete_request', 'confirm'];
export const MULTISIG_CONFIRM_METHODS = ['confirm'];
12 changes: 5 additions & 7 deletions packages/accounts/src/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { ArgumentTypeError, PositionalArgsError } from '@near-js/types';
import { LocalViewExecution } from './local-view-execution';
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import BN from 'bn.js';
import depd from 'depd';
import { AbiFunction, AbiFunctionKind, AbiRoot, AbiSerializationType } from 'near-abi';

Expand Down Expand Up @@ -75,8 +74,8 @@ const isObject = (x: any) =>
interface ChangeMethodOptions {
args: object;
methodName: string;
gas?: BN;
amount?: BN;
gas?: bigint;
amount?: bigint;
meta?: string;
callbackUrl?: string;
}
Expand Down Expand Up @@ -255,15 +254,14 @@ export class Contract {
}

/**
* Validation on arguments being a big number from bn.js
* Throws if an argument is not in BN format or otherwise invalid
* Throws if an argument is not in BigInt format or otherwise invalid
* @param argMap
*/
function validateBNLike(argMap: { [name: string]: any }) {
const bnLike = 'number, decimal string or BN';
const bnLike = 'number, decimal string or BigInt';
for (const argName of Object.keys(argMap)) {
const argValue = argMap[argName];
if (argValue && !BN.isBN(argValue) && isNaN(argValue)) {
if (argValue && typeof argValue !== "bigint" && isNaN(argValue)) {
throw new ArgumentTypeError(argName, bnLike, argValue);
}
}
Expand Down
13 changes: 6 additions & 7 deletions packages/accounts/test/account.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const { getTransactionLastResult, Logger } = require('@near-js/utils');
const { actionCreators } = require('@near-js/transactions');
const { TypedError } = require('@near-js/types');
const BN = require('bn.js');
const fs = require('fs');

const { Account, Contract } = require('../lib');
Expand Down Expand Up @@ -32,7 +31,7 @@ test('create account and then view account returns the created account', async (
const newAccountName = testUtils.generateUniqueString('test');
const newAccountPublicKey = '9AhWenZ3JddamBoyMqnTbp7yVbRuvqAv3zwfrWgfVRJE';
const { amount } = await workingAccount.state();
const newAmount = new BN(amount).div(new BN(10));
const newAmount = BigInt(amount) / BigInt(10);
await workingAccount.createAccount(newAccountName, newAccountPublicKey, newAmount);
const newAccount = new Account(nearjs.connection, newAccountName);
const state = await newAccount.state();
Expand All @@ -43,9 +42,9 @@ test('send money', async() => {
const sender = await testUtils.createAccount(nearjs);
const receiver = await testUtils.createAccount(nearjs);
const { amount: receiverAmount } = await receiver.state();
await sender.sendMoney(receiver.accountId, new BN(10000));
await sender.sendMoney(receiver.accountId, BigInt(10000));
const state = await receiver.state();
expect(state.amount).toEqual(new BN(receiverAmount).add(new BN(10000)).toString());
expect(state.amount).toEqual((BigInt(receiverAmount) + BigInt(10000)).toString());
});

test('send money through signAndSendTransaction', async() => {
Expand All @@ -54,10 +53,10 @@ test('send money through signAndSendTransaction', async() => {
const { amount: receiverAmount } = await receiver.state();
await sender.signAndSendTransaction({
receiverId: receiver.accountId,
actions: [actionCreators.transfer(new BN(10000))],
actions: [actionCreators.transfer(BigInt(10000))],
});
const state = await receiver.state();
expect(state.amount).toEqual(new BN(receiverAmount).add(new BN(10000)).toString());
expect(state.amount).toEqual((BigInt(receiverAmount) + BigInt(10000)).toString());
});

test('delete account', async() => {
Expand All @@ -74,7 +73,7 @@ test('multiple parallel transactions', async () => {
const account = new Account(workingAccount.connection, workingAccount.accountId);
// NOTE: Need to have different transactions outside of nonce, or they all succeed by being identical
// TODO: Check if randomization of exponential back off helps to do more transactions without exceeding retries
await account.sendMoney(account.accountId, new BN(i));
await account.sendMoney(account.accountId, BigInt(i));
}));
});

Expand Down
14 changes: 6 additions & 8 deletions packages/accounts/test/account_multisig.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/* global BigInt */
const { parseNearAmount } = require('@near-js/utils');
const { KeyPair } = require('@near-js/crypto');
const { InMemorySigner } = require('@near-js/signers');
const { actionCreators } = require('@near-js/transactions');
const BN = require('bn.js');
const fs = require('fs');
const semver = require('semver');

Expand Down Expand Up @@ -85,7 +83,7 @@ describe('account2fa transactions', () => {
const appPublicKey = KeyPair.fromRandom('ed25519').getPublicKey();
const appAccountId = 'foobar';
const appMethodNames = ['some_app_stuff','some_more_app_stuff'];
await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, new BN(parseNearAmount('0.25')));
await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, BigInt(parseNearAmount('0.25')));
account = await getAccount2FA(account);
const keys = await account.getAccessKeys();
expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key)
Expand All @@ -100,7 +98,7 @@ describe('account2fa transactions', () => {
const appPublicKey = KeyPair.fromRandom('ed25519').getPublicKey();
const appAccountId = 'foobar';
const appMethodNames = ['some_app_stuff', 'some_more_app_stuff'];
await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, new BN(parseNearAmount('0.25')));
await account.addKey(appPublicKey.toString(), appAccountId, appMethodNames, BigInt(parseNearAmount('0.25')));
const keys = await account.getAccessKeys();
expect(keys.find(({ public_key }) => appPublicKey.toString() === public_key)
.access_key.permission.FunctionCall.method_names).toEqual(appMethodNames);
Expand All @@ -114,9 +112,9 @@ describe('account2fa transactions', () => {
sender = await getAccount2FA(sender);
receiver = await getAccount2FA(receiver);
const { amount: receiverAmount } = await receiver.state();
await sender.sendMoney(receiver.accountId, new BN(parseNearAmount('1')));
await sender.sendMoney(receiver.accountId, BigInt(parseNearAmount('1')));
const state = await receiver.state();
expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(new BN(receiverAmount).add(new BN(parseNearAmount('0.9'))).toString()));
expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(receiverAmount)+ BigInt(parseNearAmount('0.9').toString()));
});

test('send money through signAndSendTransaction', async() => {
Expand All @@ -125,9 +123,9 @@ describe('account2fa transactions', () => {
sender = await getAccount2FA(sender);
receiver = await getAccount2FA(receiver);
const { amount: receiverAmount } = await receiver.state();
await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [transfer(new BN(parseNearAmount('1')))] });
await sender.signAndSendTransaction({ receiverId: receiver.accountId, actions: [transfer(BigInt(parseNearAmount('1')))] });
const state = await receiver.state();
expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(new BN(receiverAmount).add(new BN(parseNearAmount('0.9'))).toString()));
expect(BigInt(state.amount)).toBeGreaterThanOrEqual(BigInt(receiverAmount) + BigInt(parseNearAmount('0.9').toString()));
});

});
4 changes: 2 additions & 2 deletions packages/accounts/test/contract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ describe('viewMethod', () => {

describe('changeMethod', () => {
test('throws error message for invalid gas argument', () => {
return expect(contract.changeMethod({ a: 1 }, 'whatever')).rejects.toThrow(/Expected number, decimal string or BN for 'gas' argument, but got.+/);
return expect(contract.changeMethod({ a: 1 }, 'whatever')).rejects.toThrow(/Expected number, decimal string or BigInt for 'gas' argument, but got.+/);
});

test('gives error message for invalid amount argument', () => {
return expect(contract.changeMethod({ a: 1 }, 1000, 'whatever')).rejects.toThrow(/Expected number, decimal string or BN for 'amount' argument, but got.+/);
return expect(contract.changeMethod({ a: 1 }, 1000, 'whatever')).rejects.toThrow(/Expected number, decimal string or BigInt for 'amount' argument, but got.+/);
});

test('makes a functionCall and passes along walletCallbackUrl and walletMeta', async() => {
Expand Down
Loading
Loading