Repository: Synthetixio/synthetix-v3 (ts)

Issue:
Add comparison capability for big numbers in core-js (assert.bnEqual)

Pull request:
Add assertion util for BigNumber instances
Closes #212 

This PR adds an util for assertion on tests for comparing BigNumbers (or numbers). It implements all the methods described here: https://docs.ethers.io/v5/api/utils/bignumber/#BigNumber--BigNumber--methods--comparison-and-equivalence

Patch:
diff --git a/packages/core-js/utils/assert-bignumber.js b/packages/core-js/utils/assert-bignumber.js
new file mode 100644
index 00000000..a9e858b1
--- /dev/null
+++ b/packages/core-js/utils/assert-bignumber.js
@@ -0,0 +1,66 @@
+const { AssertionError } = require('assert');
+const { BigNumber } = require('ethers');
+
+class BigNumberAssertionError extends AssertionError {}
+
+function _createAssertBN(operator) {
+  return function _assertBn(a, b) {
+    const result = BigNumber.from(a)[operator](b);
+
+    if (!result) {
+      throw new BigNumberAssertionError({
+        actual: b,
+        expected: a,
+        operator,
+      });
+    }
+  };
+}
+
+module.exports = {
+  /**
+   * Error thrown when assertion fails
+   */
+  BigNumberAssertionError,
+
+  /**
+   * Assert if two given numbers are equal
+   * @param {(string|number|BigNumber)} a
+   * @param {(string|number|BigNumber)} b
+   */
+  eq: _createAssertBN('eq'),
+
+  /**
+   * Assert if `a` is less than than `b`
+   * @param {(string|number|BigNumber)} a
+   * @param {(string|number|BigNumber)} b
+   */
+  lt: _createAssertBN('lt'),
+
+  /**
+   * Assert if `a` is less than or equal to `b`
+   * @param {(string|number|BigNumber)} a
+   * @param {(string|number|BigNumber)} b
+   */
+  lte: _createAssertBN('lte'),
+
+  /**
+   * Assert if `a` is greater than `b`
+   * @param {(string|number|BigNumber)} a
+   * @param {(string|number|BigNumber)} b
+   */
+  gt: _createAssertBN('gt'),
+
+  /**
+   * Assert if `a` is greater than or equal to `b`
+   * @param {(string|number|BigNumber)} a
+   * @param {(string|number|BigNumber)} b
+   */
+  gte: _createAssertBN('gte'),
+
+  /**
+   * Assert if `a` is zero
+   * @param {(string|number|BigNumber)} a
+   */
+  isZero: _createAssertBN('isZero'),
+};
diff --git a/packages/core-js/utils/assertions.js b/packages/core-js/utils/assert-revert.js
similarity index 84%
rename from packages/core-js/utils/assertions.js
rename to packages/core-js/utils/assert-revert.js
index 8161ef81..03512b98 100644
--- a/packages/core-js/utils/assertions.js
+++ b/packages/core-js/utils/assert-revert.js
@@ -1,4 +1,4 @@
-async function assertRevert(tx, expectedMessage) {
+module.exports = async function assertRevert(tx, expectedMessage) {
   let error;
 
   try {
@@ -18,8 +18,4 @@ async function assertRevert(tx, expectedMessage) {
       );
     }
   }
-}
-
-module.exports = {
-  assertRevert,
 };


Test patch:
diff --git a/packages/core-contracts/test/ownership/Ownable.test.js b/packages/core-contracts/test/ownership/Ownable.test.js
index 6cf0f18a..8c7243f1 100644
--- a/packages/core-contracts/test/ownership/Ownable.test.js
+++ b/packages/core-contracts/test/ownership/Ownable.test.js
@@ -1,7 +1,7 @@
 const hre = require('hardhat');
 const assert = require('assert');
 const { ethers } = hre;
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
 const { findEvent } = require('@synthetixio/core-js/utils/events');
 
 describe('Ownable', () => {
diff --git a/packages/core-contracts/test/proxy/ForwardingProxy.test.js b/packages/core-contracts/test/proxy/ForwardingProxy.test.js
index efba852b..a8b2517e 100644
--- a/packages/core-contracts/test/proxy/ForwardingProxy.test.js
+++ b/packages/core-contracts/test/proxy/ForwardingProxy.test.js
@@ -1,6 +1,6 @@
 const { ethers } = hre;
 const assert = require('assert');
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
 
 describe('ForwardingProxy', () => {
   let Proxy, Instance, Implementation;
diff --git a/packages/core-contracts/test/proxy/UniversalProxy.test.js b/packages/core-contracts/test/proxy/UniversalProxy.test.js
index 658599c9..17538df0 100644
--- a/packages/core-contracts/test/proxy/UniversalProxy.test.js
+++ b/packages/core-contracts/test/proxy/UniversalProxy.test.js
@@ -1,6 +1,6 @@
 const { ethers } = hre;
 const assert = require('assert');
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
 const { findEvent } = require('@synthetixio/core-js/utils/events');
 
 describe('UniversalProxy', () => {
diff --git a/packages/core-contracts/test/utils/ERC20.test.js b/packages/core-contracts/test/utils/ERC20.test.js
index d731cb61..94499d7d 100644
--- a/packages/core-contracts/test/utils/ERC20.test.js
+++ b/packages/core-contracts/test/utils/ERC20.test.js
@@ -1,6 +1,7 @@
 const { ethers } = hre;
 const assert = require('assert');
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
+const bn = require('@synthetixio/core-js/utils/assert-bignumber');
 const { findEvent } = require('@synthetixio/core-js/utils/events');
 
 describe('ERC20', () => {
@@ -43,11 +44,11 @@ describe('ERC20', () => {
     });
 
     it('updates the total supply', async () => {
-      assert.equal(await ERC20.totalSupply(), totalSupply.toString());
+      bn.eq(await ERC20.totalSupply(), totalSupply);
     });
 
     it('mints the right amount to the user', async () => {
-      assert.equal(await ERC20.balanceOf(user1.address), totalSupply.toString());
+      bn.eq(await ERC20.balanceOf(user1.address), totalSupply);
     });
 
     it('emits a Transfer event', async () => {
@@ -55,7 +56,7 @@ describe('ERC20', () => {
 
       assert.equal(event.args.from, '0x0000000000000000000000000000000000000000');
       assert.equal(event.args.to, user1.address);
-      assert.equal(event.args.amount, totalSupply.toString());
+      bn.eq(event.args.amount, totalSupply);
     });
 
     describe('when tokens are burned', () => {
@@ -68,11 +69,11 @@ describe('ERC20', () => {
       });
 
       it('updates the total supply', async () => {
-        assert.equal(await ERC20.totalSupply(), newSupply.toString());
+        bn.eq(await ERC20.totalSupply(), newSupply);
       });
 
       it('reduces the user balance', async () => {
-        assert.equal(await ERC20.balanceOf(user1.address), newSupply.toString());
+        bn.eq(await ERC20.balanceOf(user1.address), newSupply);
       });
 
       it('emits a Transfer event', async () => {
@@ -80,7 +81,7 @@ describe('ERC20', () => {
 
         assert.equal(event.args.from, user1.address);
         assert.equal(event.args.to, '0x0000000000000000000000000000000000000000');
-        assert.equal(event.args.amount, tokensToBurn.toString());
+        bn.eq(event.args.amount, tokensToBurn);
       });
     });
 
@@ -110,18 +111,12 @@ describe('ERC20', () => {
         });
 
         it('does not alter the total supply', async () => {
-          assert.equal(await ERC20.totalSupply(), currentSupply.toString());
+          bn.eq(await ERC20.totalSupply(), currentSupply);
         });
 
         it('reduces the sender balance and increases the receiver balance', async () => {
-          assert.equal(
-            await ERC20.balanceOf(user1.address),
-            user1Balance.sub(transferAmount).toString()
-          );
-          assert.equal(
-            await ERC20.balanceOf(user2.address),
-            user2Balance.add(transferAmount).toString()
-          );
+          bn.eq(await ERC20.balanceOf(user1.address), user1Balance.sub(transferAmount));
+          bn.eq(await ERC20.balanceOf(user2.address), user2Balance.add(transferAmount));
         });
 
         it('emits a Transfer event', async () => {
@@ -129,7 +124,7 @@ describe('ERC20', () => {
 
           assert.equal(event.args.from, user1.address);
           assert.equal(event.args.to, user2.address);
-          assert.equal(event.args.amount, transferAmount.toString());
+          bn.eq(event.args.amount, transferAmount);
         });
       });
     });
@@ -149,10 +144,7 @@ describe('ERC20', () => {
       });
 
       it('sets the right allowance', async () => {
-        assert.equal(
-          await ERC20.allowance(user1.address, user2.address),
-          approvalAmount.toString()
-        );
+        bn.eq(await ERC20.allowance(user1.address, user2.address), approvalAmount);
       });
 
       it('emits an Approval event', async () => {
@@ -160,7 +152,7 @@ describe('ERC20', () => {
 
         assert.equal(event.args.owner, user1.address);
         assert.equal(event.args.spender, user2.address);
-        assert.equal(event.args.amount, approvalAmount.toString());
+        bn.eq(event.args.amount, approvalAmount);
       });
 
       describe('when trying to transfer more than the amount approved', () => {
@@ -193,14 +185,8 @@ describe('ERC20', () => {
         });
 
         it('updates the user balances accordingly', async () => {
-          assert.equal(
-            await ERC20.balanceOf(user1.address),
-            user1Balance.sub(approvalAmount).toString()
-          );
-          assert.equal(
-            await ERC20.balanceOf(user2.address),
-            user2Balance.add(approvalAmount).toString()
-          );
+          bn.eq(await ERC20.balanceOf(user1.address), user1Balance.sub(approvalAmount));
+          bn.eq(await ERC20.balanceOf(user2.address), user2Balance.add(approvalAmount));
         });
 
         it('emits a Transfer event', async () => {
@@ -208,7 +194,7 @@ describe('ERC20', () => {
 
           assert.equal(event.args.from, user1.address);
           assert.equal(event.args.to, user2.address);
-          assert.equal(event.args.amount, transferFromAmount.toString());
+          bn.eq(event.args.amount, transferFromAmount);
         });
       });
     });
diff --git a/packages/core-js/test/utils/assert-bignumber.test.js b/packages/core-js/test/utils/assert-bignumber.test.js
new file mode 100644
index 00000000..adb61a7d
--- /dev/null
+++ b/packages/core-js/test/utils/assert-bignumber.test.js
@@ -0,0 +1,58 @@
+const { BigNumber } = require('ethers');
+const bn = require('../../utils/assert-bignumber');
+
+function not(operator, a, b) {
+  try {
+    bn[operator](a, b);
+    throw new Error(`Expected to throw: bn.${operator}(${a}, ${b})`);
+  } catch (err) {
+    if (err instanceof bn.BigNumberAssertionError) return;
+    throw err;
+  }
+}
+
+describe('utils/assert-bignumber.js', function () {
+  it('#eq', function () {
+    bn.eq(BigNumber.from(12), BigNumber.from('12'));
+    bn.eq(BigNumber.from(12), BigNumber.from(12));
+    bn.eq(BigNumber.from(12), 12);
+    bn.eq('12', 12);
+    bn.eq('12', '12');
+    not('eq', BigNumber.from(12), BigNumber.from('72'));
+    not('eq', BigNumber.from(13), BigNumber.from(12));
+    not('eq', BigNumber.from(12), 5);
+    not('eq', '12', 0x123123123);
+    not('eq', '12', '1');
+  });
+
+  it('#lt', function () {
+    bn.lt(BigNumber.from(12), BigNumber.from(24));
+    bn.lt(BigNumber.from(13), BigNumber.from(14));
+    not('lt', BigNumber.from(12), BigNumber.from(12));
+    not('lt', BigNumber.from(12), BigNumber.from(11));
+  });
+
+  it('#lte', function () {
+    bn.lte(BigNumber.from(12), BigNumber.from(24));
+    bn.lte(BigNumber.from(13), BigNumber.from(14));
+    bn.lte(BigNumber.from(13), BigNumber.from(13));
+    not('lte', BigNumber.from(15), BigNumber.from(14));
+  });
+
+  it('#gt', function () {
+    bn.gt(BigNumber.from(15), BigNumber.from(13));
+    not('gt', BigNumber.from(15), BigNumber.from(15));
+    not('gt', BigNumber.from(15), BigNumber.from(16));
+  });
+
+  it('#gte', function () {
+    bn.gte(BigNumber.from(15), BigNumber.from(13));
+    bn.gte(BigNumber.from(15), BigNumber.from(15));
+    not('gte', BigNumber.from(14), BigNumber.from(15));
+  });
+
+  it('#isZero', function () {
+    bn.isZero(1);
+    not('isZero', BigNumber.from(1));
+  });
+});
diff --git a/packages/core-js/test/utils/assertions.test.js b/packages/core-js/test/utils/assert-revert.test.js
similarity index 92%
rename from packages/core-js/test/utils/assertions.test.js
rename to packages/core-js/test/utils/assert-revert.test.js
index 94971d98..0be0ca56 100644
--- a/packages/core-js/test/utils/assertions.test.js
+++ b/packages/core-js/test/utils/assert-revert.test.js
@@ -1,5 +1,5 @@
 const { rejects } = require('assert/strict');
-const { assertRevert } = require('../../utils/assertions');
+const assertRevert = require('../../utils/assert-revert');
 
 function mockValidTx() {
   return Promise.resolve({ wait: () => Promise.resolve() });
@@ -9,7 +9,7 @@ function mockRevertingTx(errorMsg = '') {
   return Promise.resolve({ wait: () => Promise.reject(new Error(errorMsg)) });
 }
 
-describe('utils/assertions.js', function () {
+describe('utils/assert-revert.js', function () {
   describe('#assertRevert', function () {
     it('resolves when correctly reverting', async function () {
       await assertRevert(mockRevertingTx(), '');
diff --git a/packages/deployer/test/fixture-projects/sample-project/test/OwnerModule.test.js b/packages/deployer/test/fixture-projects/sample-project/test/OwnerModule.test.js
index 1392bac5..4ee07292 100644
--- a/packages/deployer/test/fixture-projects/sample-project/test/OwnerModule.test.js
+++ b/packages/deployer/test/fixture-projects/sample-project/test/OwnerModule.test.js
@@ -2,7 +2,7 @@ const hre = require('hardhat');
 const assert = require('assert');
 const { ethers } = hre;
 const { getProxyAddress } = require('@synthetixio/deployer/utils/deployments');
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
 const { findEvent } = require('@synthetixio/core-js/utils/events');
 const bootstrap = require('./helpers/bootstrap');
 
diff --git a/packages/deployer/test/fixture-projects/sample-project/test/SettingsModule.test.js b/packages/deployer/test/fixture-projects/sample-project/test/SettingsModule.test.js
index 17c63ff4..abafc0cd 100644
--- a/packages/deployer/test/fixture-projects/sample-project/test/SettingsModule.test.js
+++ b/packages/deployer/test/fixture-projects/sample-project/test/SettingsModule.test.js
@@ -2,7 +2,7 @@ const hre = require('hardhat');
 const assert = require('assert');
 const { ethers } = hre;
 const { getProxyAddress } = require('@synthetixio/deployer/utils/deployments');
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
 const bootstrap = require('./helpers/bootstrap');
 
 describe('SettingsModule', () => {
diff --git a/packages/deployer/test/fixture-projects/sample-project/test/UpgradeModule.test.js b/packages/deployer/test/fixture-projects/sample-project/test/UpgradeModule.test.js
index acbc854e..d4723d87 100644
--- a/packages/deployer/test/fixture-projects/sample-project/test/UpgradeModule.test.js
+++ b/packages/deployer/test/fixture-projects/sample-project/test/UpgradeModule.test.js
@@ -2,7 +2,7 @@ const hre = require('hardhat');
 const assert = require('assert');
 const { ethers } = hre;
 const { getProxyAddress, getRouterAddress } = require('@synthetixio/deployer/utils/deployments');
-const { assertRevert } = require('@synthetixio/core-js/utils/assertions');
+const assertRevert = require('@synthetixio/core-js/utils/assert-revert');
 const bootstrap = require('./helpers/bootstrap');
 
 describe('UpgradeModule', () => {