UNPKG

flowbatcher

Version:

Save gas by batching multiple ETH and ERC-20 transactions into a single operation, optimizing efficiency and reducing costs.

273 lines (231 loc) 8.96 kB
/** * Unit Tests for library functions * includes the following methods: * - `batchTransferERC20`: Handles ERC-20 token transfers to multiple recipients in a single transaction. * - `executeBatchTransactions`: Handles both ERC-20 token and native ETH batch transfers. * - Gas estimation: Tests for estimating gas fees for ERC-20 batch transfers. * - User confirmation: Verifies that the user is prompted to confirm the transaction before execution. * - Error handling: Tests for handling invalid input and gas estimation failures. * * The tests use **Sinon** for mocking external dependencies, including contract methods, gas estimation, and user inputs. */ const { expect } = require("chai") const sinon = require("sinon") const loggerLib = require("../src/lib/logger.lib") const errorUtil = require("../src/utils/error.utils") const loggerColourEnum= require("../src/utils/logger.colour.enum") const logTypeEnum = require("../src/enums/log.type.enum") describe("Logger Library", () => { let logWithColorSpy let throwErrorSpy beforeEach(() => { // Spy on logWithColor and throwError logWithColorSpy = sinon.spy(loggerLib, "logWithColor") throwErrorSpy = sinon.spy(errorUtil, "throwError") }) afterEach(() => { // Restore all spies after each test sinon.restore() }) it("should log message with default color (green)", () => { const message = "This is an info log" const expectedLogData = { date: sinon.match.string, // Date should be a string message: message, data: {}, } // Call the log function loggerLib.log(message) // Assert logWithColor was called with the correct parameters expect(logWithColorSpy.calledOnce).to.be.true expect(logWithColorSpy.firstCall.args[0]).to.equal(message) expect(logWithColorSpy.firstCall.args[1]).to.equal(logTypeEnum.INFO) expect(logWithColorSpy.firstCall.args[2]).to.deep.equal({}) }) it("should log message with custom color (red)", () => { const message = "This is an error log" const expectedLogData = { date: sinon.match.string, // Date should be a string message: message, data: {}, } // Call the logWithColor function directly loggerLib.logWithColor(message, logTypeEnum.ERR) // Assert logWithColor was called with the correct parameters expect(logWithColorSpy.calledOnce).to.be.true expect(logWithColorSpy.firstCall.args[0]).to.equal(message) expect(logWithColorSpy.firstCall.args[1]).to.equal(logTypeEnum.ERR) expect(logWithColorSpy.firstCall.args[2]).to.deep.equal({}) }) it("should throw error if message is empty in log function", () => { const emptyMessage = "" // Call log with an empty message loggerLib.log(emptyMessage) // Assert that throwError was called expect(throwErrorSpy.calledOnce).to.be.true expect(throwErrorSpy.firstCall.args[0]).to.equal( "Message is empty! message: ", ) }) it("should throw error if message or type is empty in logWithColor function", () => { const emptyMessage = "" const validType = logTypeEnum.INFO // Call logWithColor with an empty message loggerLib.logWithColor(emptyMessage, validType) // Assert that throwError was called expect(throwErrorSpy.calledOnce).to.be.true expect(throwErrorSpy.firstCall.args[0]).to.equal( "Message or type is empty! message: , type: info", ) }) it("should throw error if type is empty in logWithColor function", () => { const message = "This is a log with no type" const emptyType = "" // Call logWithColor with an empty type loggerLib.logWithColor(message, emptyType) // Assert that throwError was called expect(throwErrorSpy.calledOnce).to.be.true expect(throwErrorSpy.firstCall.args[0]).to.equal( "Message or type is empty! message: This is a log with no type , type: ", ) }) }) // ethers lib describe("Ethers Library - approveTokens and executeBatchTransactions", () => { let logWithColorSpy let mockERC20Contract let mockBatchTransferContract let mockProvider let mockSigner beforeEach(() => { // Spy on logWithColor logWithColorSpy = sinon.spy(loggerLib, "logWithColor") // Mock ERC-20 contract mockERC20Contract = { decimals: sinon.stub().resolves(18), approve: sinon.stub().resolves({ hash: "0xFakeTxHash" }), } // Mock batch transfer contract mockBatchTransferContract = { batchTransfer: sinon.stub().resolves({ hash: "0xFakeBatchTxHash" }), } // Mock provider and signer mockProvider = new ethers.JsonRpcProvider("https://fake-rpc-url.com") mockSigner = new ethers.Wallet("0xfakeprivatekey", mockProvider) // Set global keys for ERC-20 contract and batch transfer contract generalUseUtil.setGlobalKey( globalKeysEnum.ERC20_CONTRACT, mockERC20Contract, ) generalUseUtil.setGlobalKey( globalKeysEnum.BATCH_TRANSFER_CONTRACT, mockBatchTransferContract, ) generalUseUtil.setGlobalKey(globalKeysEnum.PROVIDER, mockProvider) generalUseUtil.setGlobalKey(globalKeysEnum.SIGNER, mockSigner) }) afterEach(() => { // Restore all spies after each test sinon.restore() }) it("should approve tokens for batch transfer", async () => { const spender = "0xSpenderAddress" const amount = 100 // Call approveTokens const tx = await ethersLib.approveTokens(spender, amount) // Assert that approve was called on the ERC-20 contract expect(mockERC20Contract.approve.calledOnce).to.be.true expect(mockERC20Contract.approve.firstCall.args[0]).to.equal(spender) expect(mockERC20Contract.approve.firstCall.args[1]).to.equal( ethers.parseUnits(amount.toString(), 18), ) // Assert that the logger function was called correctly expect( logWithColorSpy.calledWith( "Tokens approved successfully!", loggerColourEnum.INFO, ), ).to.be.true expect(tx).to.deep.equal({ hash: "0xFakeTxHash" }) }) it("should execute ERC-20 batch transfer correctly", async () => { const recipients = ["0xRecipient1", "0xRecipient2"] const amounts = [100, 50] const tokenAddress = "0xTokenAddress" // Call executeBatchTransactions for ERC-20 const tx = await ethersLib.executeBatchTransactions( recipients, amounts, tokenAddress, ) // Assert that batchTransfer was called on the batch contract expect(mockBatchTransferContract.batchTransfer.calledOnce).to.be.true expect( mockBatchTransferContract.batchTransfer.firstCall.args[0], ).to.deep.equal(recipients) expect( mockBatchTransferContract.batchTransfer.firstCall.args[1], ).to.deep.equal(amounts) expect(mockBatchTransferContract.batchTransfer.firstCall.args[2]).to.equal( tokenAddress, ) // Assert that the logger function was called correctly expect( logWithColorSpy.calledWith( `Batch ERC-20 Transfer tx hash: 0xFakeBatchTxHash`, loggerColourEnum.INFO, ), ).to.be.true expect( logWithColorSpy.calledWith( "✅ ERC-20 Batch Transfer completed!", loggerColourEnum.INFO, ), ).to.be.true expect(tx).to.deep.equal({ hash: "0xFakeBatchTxHash" }) }) it("should execute native ETH batch transfer correctly", async () => { const recipients = ["0xRecipient1", "0xRecipient2"] const amounts = [0.1, 0.2] // Call executeBatchTransactions for native ETH const tx = await ethersLib.executeBatchTransactions( recipients, amounts, ethers.ZeroAddress, true, ) // Assert that batchTransfer was called on the batch contract expect(mockBatchTransferContract.batchTransfer.calledOnce).to.be.true expect( mockBatchTransferContract.batchTransfer.firstCall.args[0], ).to.deep.equal(recipients) expect( mockBatchTransferContract.batchTransfer.firstCall.args[1], ).to.deep.equal(amounts) expect(mockBatchTransferContract.batchTransfer.firstCall.args[2]).to.equal( ethers.ZeroAddress, ) // Assert that the logger function was called correctly expect( logWithColorSpy.calledWith( `Batch Native Transfer tx hash: 0xFakeBatchTxHash`, loggerColourEnum.INFO, ), ).to.be.true expect( logWithColorSpy.calledWith( "✅ Native Batch Transfer completed!", loggerColourEnum.INFO, ), ).to.be.true expect(tx).to.deep.equal({ hash: "0xFakeBatchTxHash" }) }) it("should throw an error if the recipients and amounts arrays are not the same length", async () => { const recipients = ["0xRecipient1", "0xRecipient2"] const amounts = [100] // Mismatch in lengths // Expect an error to be thrown await expect( ethersLib.executeBatchTransactions(recipients, amounts, "0xTokenAddress"), ).to.be.rejectedWith("ArraysLengthMismatch") }) })