From e73949a31a1e00f908a4e3e2e0b5595a9e9fa2ff Mon Sep 17 00:00:00 2001 From: JohnGuilding Date: Wed, 8 Mar 2023 16:49:48 +0000 Subject: [PATCH] Cleanup test code & add contract interaction gas estimate tests --- contracts/clients/test/BlsProvider.test.ts | 84 +++++++- .../test-integration/BlsProvider.test.ts | 163 +++------------- .../BlsProviderContractInteraction.test.ts | 7 + contracts/test-integration/BlsSigner.test.ts | 140 ++++++-------- .../BlsSignerContractInteraction.test.ts | 179 +++++++++++++----- 5 files changed, 299 insertions(+), 274 deletions(-) diff --git a/contracts/clients/test/BlsProvider.test.ts b/contracts/clients/test/BlsProvider.test.ts index b1578fd91..2f378a3ad 100644 --- a/contracts/clients/test/BlsProvider.test.ts +++ b/contracts/clients/test/BlsProvider.test.ts @@ -25,7 +25,7 @@ describe("BlsProvider", () => { rpcUrl = "http://localhost:8545"; network = { name: "localhost", - chainId: 0x7a69, + chainId: 0x539, // 1337 }; privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); @@ -90,6 +90,7 @@ describe("BlsProvider", () => { // Arrange const transaction = { value: parseEther("1"), + // Explicitly omit 'to' }; // Act @@ -107,6 +108,7 @@ describe("BlsProvider", () => { const transaction = { value: parseEther("1"), to: ethers.Wallet.createRandom().address, + // Explicitly omit 'from' }; // Act @@ -129,4 +131,84 @@ describe("BlsProvider", () => { // Assert expect(connection).to.deep.equal(expectedConnection); }); + + it("should throw an error when sending an invalid signed transaction", async () => { + // Arrange + const invalidTransaction = "Invalid signed transaction"; + + // Act + const result = async () => + await blsProvider.sendTransaction(invalidTransaction); + + // Assert + await expect(result()).to.be.rejectedWith( + Error, + "Unexpected token I in JSON at position 0", + ); + }); + + it("should get the polling interval", async () => { + // Arrange + const expectedpollingInterval = 4000; // default + const updatedInterval = 1000; + + // Act + const pollingInterval = blsProvider.pollingInterval; + blsProvider.pollingInterval = updatedInterval; + const updatedPollingInterval = blsProvider.pollingInterval; + + // Assert + expect(pollingInterval).to.equal(expectedpollingInterval); + expect(updatedPollingInterval).to.equal(updatedInterval); + }); + + it("should get the event listener count and remove all listeners", async () => { + blsProvider.on("block", () => {}); + blsProvider.on("error", () => {}); + expect(blsProvider.listenerCount("block")).to.equal(1); + expect(blsProvider.listenerCount("error")).to.equal(1); + expect(blsProvider.listenerCount()).to.equal(2); + + blsProvider.removeAllListeners(); + expect(blsProvider.listenerCount("block")).to.equal(0); + expect(blsProvider.listenerCount("error")).to.equal(0); + expect(blsProvider.listenerCount()).to.equal(0); + }); + + it("should return true and an array of listeners if polling", async () => { + // Arrange + const expectedListener = () => {}; + + // Act + blsProvider.on("block", expectedListener); + const listeners = blsProvider.listeners("block"); + const isPolling = blsProvider.polling; + blsProvider.removeAllListeners(); + + // Assert + expect(listeners).to.deep.equal([expectedListener]); + expect(isPolling).to.be.true; + }); + + it("should be a provider", async () => { + // Arrange & Act + const isProvider = Experimental.BlsProvider.isProvider(blsProvider); + const isProviderWithInvalidProvider = + Experimental.BlsProvider.isProvider(blsSigner); + + // Assert + expect(isProvider).to.equal(true); + expect(isProviderWithInvalidProvider).to.equal(false); + }); + + it("should a return a promise which will stall until the network has heen established", async () => { + // Arrange + const expectedReady = { name: "localhost", chainId: 1337 }; + + // Act + const ready = await blsProvider.ready; + + // Assert + expect(ready).to.deep.equal(expectedReady); + }); }); diff --git a/contracts/test-integration/BlsProvider.test.ts b/contracts/test-integration/BlsProvider.test.ts index b408c805d..6e21f0eb5 100644 --- a/contracts/test-integration/BlsProvider.test.ts +++ b/contracts/test-integration/BlsProvider.test.ts @@ -84,12 +84,11 @@ describe("BlsProvider", () => { it("should estimate gas without throwing an error", async () => { // Arrange - const recipient = ethers.Wallet.createRandom().address; - const transactionAmount = parseEther("1"); + const getAddressPromise = blsSigner.getAddress(); const transactionRequest = { - to: recipient, - value: transactionAmount, - from: await blsSigner.getAddress(), + to: ethers.Wallet.createRandom().address, + value: parseEther("1"), + from: getAddressPromise, }; // Act @@ -106,16 +105,11 @@ describe("BlsProvider", () => { const expectedBalance = parseEther("1"); const balanceBefore = await blsProvider.getBalance(recipient); - const unsignedTransaction = { + const signedTransaction = await blsSigner.signTransaction({ value: expectedBalance.toString(), to: recipient, data: "0x", - from: await blsSigner.getAddress(), - }; - - const signedTransaction = await blsSigner.signTransaction( - unsignedTransaction, - ); + }); // Act const transaction = await blsProvider.sendTransaction(signedTransaction); @@ -130,18 +124,11 @@ describe("BlsProvider", () => { it("should get the account nonce when the signer constructs the transaction response", async () => { // Arrange const spy = chai.spy.on(BlsWalletWrapper, "Nonce"); - const recipient = ethers.Wallet.createRandom().address; - const expectedBalance = parseEther("1"); - - const unsignedTransaction = { - value: expectedBalance.toString(), - to: recipient, + const signedTransaction = await blsSigner.signTransaction({ + value: parseEther("1"), + to: ethers.Wallet.createRandom().address, data: "0x", - from: await blsSigner.getAddress(), - }; - const signedTransaction = await blsSigner.signTransaction( - unsignedTransaction, - ); + }); // Act await blsProvider.sendTransaction(signedTransaction); @@ -160,7 +147,6 @@ describe("BlsProvider", () => { value: parseEther("1"), to: ethers.Wallet.createRandom().address, data: "0x", - from: await blsSigner.getAddress(), }); const userBundle = JSON.parse(signedTransaction); @@ -177,21 +163,6 @@ describe("BlsProvider", () => { ); }); - it("should throw an error when sending an invalid signed transaction", async () => { - // Arrange - const invalidTransaction = "Invalid signed transaction"; - - // Act - const result = async () => - await blsProvider.sendTransaction(invalidTransaction); - - // Assert - await expect(result()).to.be.rejectedWith( - Error, - "Unexpected token I in JSON at position 0", - ); - }); - it("should throw an error when the transaction receipt cannot be found", async () => { // Arrange const randomBytes = ethers.utils.randomBytes(20); @@ -215,11 +186,9 @@ describe("BlsProvider", () => { it("should wait for a transaction and resolve once transaction hash is included in the block", async () => { // Arrange - const recipient = ethers.Wallet.createRandom().address; const transactionResponse = await blsSigner.sendTransaction({ - to: recipient, + to: ethers.Wallet.createRandom().address, value: parseEther("1"), - from: await blsSigner.getAddress(), }); const expectedToAddress = "0x689A095B4507Bfa302eef8551F90fB322B3451c6"; // Verification Gateway address @@ -278,11 +247,9 @@ describe("BlsProvider", () => { it("should retrieve a transaction receipt given a valid hash", async () => { // Arrange - const recipient = ethers.Wallet.createRandom().address; const transactionResponse = await blsSigner.sendTransaction({ - to: recipient, + to: ethers.Wallet.createRandom().address, value: parseEther("1"), - from: await blsSigner.getAddress(), }); const expectedToAddress = "0x689A095B4507Bfa302eef8551F90fB322B3451c6"; // Verification Gateway address @@ -339,12 +306,9 @@ describe("BlsProvider", () => { it("gets a transaction given a valid transaction hash", async () => { // Arrange - const recipient = ethers.Wallet.createRandom().address; - const transactionAmount = parseEther("1"); const transactionRequest = { - to: recipient, - value: transactionAmount, - from: await blsSigner.getAddress(), + to: ethers.Wallet.createRandom().address, + value: parseEther("1"), }; const expectedTransactionResponse = await blsSigner.sendTransaction( @@ -436,69 +400,9 @@ describe("BlsProvider", () => { expect(accounts).to.deep.equal(expectedAccounts); }); - it("should get the polling interval", async () => { - // Arrange - const expectedpollingInterval = 4000; // default - const updatedInterval = 1000; - - // Act - const pollingInterval = blsProvider.pollingInterval; - blsProvider.pollingInterval = updatedInterval; - const updatedPollingInterval = blsProvider.pollingInterval; - - // Assert - expect(pollingInterval).to.equal(expectedpollingInterval); - expect(updatedPollingInterval).to.equal(updatedInterval); - }); - - it("should get the event listener count and remove all listeners", async () => { - blsProvider.on("block", () => {}); - blsProvider.on("error", () => {}); - expect(blsProvider.listenerCount("block")).to.equal(1); - expect(blsProvider.listenerCount("error")).to.equal(1); - expect(blsProvider.listenerCount()).to.equal(2); - - blsProvider.removeAllListeners(); - expect(blsProvider.listenerCount("block")).to.equal(0); - expect(blsProvider.listenerCount("error")).to.equal(0); - expect(blsProvider.listenerCount()).to.equal(0); - }); - - it("should return true and an array of listeners if polling", async () => { - // Arrange - const expectedListener = () => {}; - - // Act - blsProvider.on("block", expectedListener); - const listeners = blsProvider.listeners("block"); - const isPolling = blsProvider.polling; - blsProvider.removeAllListeners(); - - // Assert - expect(listeners).to.deep.equal([expectedListener]); - expect(isPolling).to.be.true; - }); - - it("should be a provider", async () => { - // Arrange & Act - const isProvider = Experimental.BlsProvider.isProvider(blsProvider); - const isProviderWithInvalidProvider = - Experimental.BlsProvider.isProvider(blsSigner); - - // Assert - expect(isProvider).to.equal(true); - expect(isProviderWithInvalidProvider).to.equal(false); - }); - it("should return the number of transactions an address has sent", async function () { // Arrange - const transaction = { - value: BigNumber.from(1), - to: ethers.Wallet.createRandom().address, - from: await blsSigner.getAddress(), - }; const address = await blsSigner.getAddress(); - const expectedFirstTransactionCount = 0; const expectedSecondTransactionCount = 1; @@ -507,7 +411,10 @@ describe("BlsProvider", () => { address, ); - const sendTransaction = await blsSigner.sendTransaction(transaction); + const sendTransaction = await blsSigner.sendTransaction({ + value: BigNumber.from(1), + to: ethers.Wallet.createRandom().address, + }); await sendTransaction.wait(); const secondTransactionCount = await blsProvider.getTransactionCount( @@ -526,7 +433,6 @@ describe("BlsProvider", () => { const sendTransaction = await blsSigner.sendTransaction({ value: BigNumber.from(1), to: ethers.Wallet.createRandom().address, - from: await blsSigner.getAddress(), }); await sendTransaction.wait(); @@ -684,17 +590,6 @@ describe("BlsProvider", () => { ); expect(feeData.gasPrice).to.deep.equal(expectedFeeData.gasPrice); }); - - it("should a return a promise which will stall until the network has heen established", async () => { - // Arrange - const expectedReady = { name: "localhost", chainId: 1337 }; - - // Act - const ready = await blsProvider.ready; - - // Assert - expect(ready).to.deep.equal(expectedReady); - }); }); describe("JsonRpcProvider", () => { @@ -718,13 +613,11 @@ describe("JsonRpcProvider", () => { regularProvider, ); - const transaction = { + // Act + const result = await regularProvider.call({ to: testERC20.address, data: testERC20.interface.encodeFunctionData("totalSupply"), - }; - - // Act - const result = await regularProvider.call(transaction); + }); // Assert expect(formatEther(result)).to.equal(expectedSupply); @@ -732,16 +625,10 @@ describe("JsonRpcProvider", () => { it("gets a transaction given a valid transaction hash", async () => { // Arrange - const recipient = ethers.Wallet.createRandom().address; - const transactionAmount = parseEther("1"); - const transactionRequest = { - to: recipient, - value: transactionAmount, - }; - - const expectedTransactionResponse = await wallet.sendTransaction( - transactionRequest, - ); + const expectedTransactionResponse = await wallet.sendTransaction({ + to: ethers.Wallet.createRandom().address, + value: parseEther("1"), + }); // Act const transactionResponse = await regularProvider.getTransaction( diff --git a/contracts/test-integration/BlsProviderContractInteraction.test.ts b/contracts/test-integration/BlsProviderContractInteraction.test.ts index 5da25318c..aaeeb56d2 100644 --- a/contracts/test-integration/BlsProviderContractInteraction.test.ts +++ b/contracts/test-integration/BlsProviderContractInteraction.test.ts @@ -66,18 +66,25 @@ describe("Provider tests", function () { }); it("balanceOf() call", async () => { + // Arrange & Act const balance = await mockERC20.connect(blsProvider).balanceOf(recipient); + + // Assert expect(balance).to.equal(tokenSupply.div(2)); }); it("calls balanceOf successfully after instantiating Contract class with BlsProvider", async () => { + // Arrange const erc20 = new ethers.Contract( mockERC20.address, mockERC20.interface, blsProvider, ); + + // Act const balance = await erc20.balanceOf(recipient); + // Assert expect(erc20.provider).to.equal(blsProvider); expect(balance).to.equal(tokenSupply.div(2)); }); diff --git a/contracts/test-integration/BlsSigner.test.ts b/contracts/test-integration/BlsSigner.test.ts index dd9b5031d..17bb65ddf 100644 --- a/contracts/test-integration/BlsSigner.test.ts +++ b/contracts/test-integration/BlsSigner.test.ts @@ -16,7 +16,6 @@ import { ActionData, BlsWalletWrapper, NetworkConfig, - // eslint-disable-next-line camelcase MockERC20__factory, AggregatorUtilities__factory, } from "../clients/src"; @@ -84,7 +83,6 @@ describe("BlsSigner", () => { const transaction = await blsSigner.sendTransaction({ to: recipient, value: expectedBalance, - from: await blsSigner.getAddress(), }); await transaction.wait(); @@ -103,7 +101,6 @@ describe("BlsSigner", () => { await blsSigner.sendTransaction({ to: ethers.Wallet.createRandom().address, value: invalidValue, - from: await blsSigner.getAddress(), }); // Assert @@ -128,7 +125,6 @@ describe("BlsSigner", () => { to: recipient, value: transactionAmount, data: "0x", - from: await blsSigner.getAddress(), }); // Assert @@ -236,6 +232,8 @@ describe("BlsSigner", () => { blsSigner, ); + // BlsWalletWrapper.getRandomBlsPrivateKey from "estimateGas" method results in slightly different + // fee estimates. Which leads to a different signature. This fake avoids signature mismatch by stubbing a constant value. sinon.replace( BlsWalletWrapper, "getRandomBlsPrivateKey", @@ -282,15 +280,12 @@ describe("BlsSigner", () => { // Arrange const invalidEthValue = parseEther("-1"); - const unsignedTransaction = { - value: invalidEthValue, - to: ethers.Wallet.createRandom().address, - from: await blsSigner.getAddress(), - }; - // Act const result = async () => - await blsSigner.signTransaction(unsignedTransaction); + await blsSigner.signTransaction({ + value: invalidEthValue, + to: ethers.Wallet.createRandom().address, + }); // Assert await expect(result()).to.be.rejectedWith( @@ -303,13 +298,12 @@ describe("BlsSigner", () => { // Arrange const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - to: recipient, - value: transactionAmount, - }; // Act - const result = blsSigner.checkTransaction(transaction); + const result = blsSigner.checkTransaction({ + to: recipient, + value: transactionAmount, + }); // Assert const resolvedResult = await resolveProperties(result); @@ -326,13 +320,12 @@ describe("BlsSigner", () => { // Arrange const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - to: recipient, - value: transactionAmount, - }; // Act - const result = await blsSigner.populateTransaction(transaction); + const result = await blsSigner.populateTransaction({ + to: recipient, + value: transactionAmount, + }); // Assert expect(result).to.be.an("object").that.includes({ @@ -385,29 +378,25 @@ describe("BlsSigner", () => { }); it("should await the init promise when connecting to an unchecked bls signer", async () => { - // Arrange & Act + // Arrange const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey(); const newBlsSigner = blsProvider.getSigner(newPrivateKey); const uncheckedBlsSigner = newBlsSigner.connectUnchecked(); await fundedWallet.sendTransaction({ to: await uncheckedBlsSigner.getAddress(), - value: parseEther("2"), + value: parseEther("1.1"), }); const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - value: transactionAmount, - to: recipient, - from: await uncheckedBlsSigner.getAddress(), - }; const balanceBefore = await blsProvider.getBalance(recipient); // Act - const uncheckedResponse = await uncheckedBlsSigner.sendTransaction( - transaction, - ); + const uncheckedResponse = await uncheckedBlsSigner.sendTransaction({ + value: transactionAmount, + to: recipient, + }); await uncheckedResponse.wait(); // Assert @@ -416,9 +405,7 @@ describe("BlsSigner", () => { ).to.equal(transactionAmount); }); - // TODO (merge-ok) https://github.com/web3well/bls-wallet/issues/427 - // This test is identical to the above test except this one uses a new instance of a provider, yet fails to find the tx receipt - it.skip("should get the transaction receipt when using a new provider and connecting to an unchecked bls signer", async () => { + it("should get the transaction receipt when using a new provider and connecting to an unchecked bls signer", async () => { // Arrange & Act const newBlsProvider = new Experimental.BlsProvider( aggregatorUrl, @@ -431,19 +418,20 @@ describe("BlsSigner", () => { const newBlsSigner = newBlsProvider.getSigner(newPrivateKey); const uncheckedBlsSigner = newBlsSigner.connectUnchecked(); + await fundedWallet.sendTransaction({ + to: await uncheckedBlsSigner.getAddress(), + value: parseEther("1.1"), + }); + const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - value: transactionAmount, - to: recipient, - from: await blsSigner.getAddress(), - }; const balanceBefore = await blsProvider.getBalance(recipient); // Act - const uncheckedResponse = await uncheckedBlsSigner.sendTransaction( - transaction, - ); + const uncheckedResponse = await uncheckedBlsSigner.sendTransaction({ + value: transactionAmount, + to: recipient, + }); await uncheckedResponse.wait(); // Assert @@ -456,17 +444,13 @@ describe("BlsSigner", () => { // Arrange const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - value: transactionAmount, - to: recipient, - from: await blsSigner.getAddress(), - }; const balanceBefore = await blsProvider.getBalance(recipient); // Act - const uncheckedTransactionHash = await blsSigner.sendUncheckedTransaction( - transaction, - ); + const uncheckedTransactionHash = await blsSigner.sendUncheckedTransaction({ + value: transactionAmount, + to: recipient, + }); await blsProvider.getTransactionReceipt(uncheckedTransactionHash); // Assert @@ -481,23 +465,18 @@ describe("BlsSigner", () => { const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - value: transactionAmount, - to: recipient, - from: await blsSigner.getAddress(), - }; const balanceBefore = await blsProvider.getBalance(recipient); // Act - const uncheckedResponse = await uncheckedBlsSigner.sendTransaction( - transaction, - ); + const uncheckedResponse = await uncheckedBlsSigner.sendTransaction({ + value: transactionAmount, + to: recipient, + }); await uncheckedResponse.wait(); // Assert expect(uncheckedResponse).to.be.an("object").that.includes({ hash: uncheckedResponse.hash, - nonce: 1, data: "", chainId: 0, confirmations: 0, @@ -507,6 +486,7 @@ describe("BlsSigner", () => { expect(uncheckedResponse.gasLimit).to.equal(BigNumber.from("0")); expect(uncheckedResponse.gasPrice).to.equal(BigNumber.from("0")); expect(uncheckedResponse.value).to.equal(BigNumber.from("0")); + expect(isNaN(uncheckedResponse.nonce)).to.be.true; expect( (await blsProvider.getBalance(recipient)).sub(balanceBefore), @@ -575,9 +555,8 @@ describe("BlsSigner", () => { const expectedTransactionCount = 0; const sendTransaction = await blsSigner.sendTransaction({ - value: BigNumber.from(1), + value: parseEther("1"), to: ethers.Wallet.createRandom().address, - from: await blsSigner.getAddress(), }); await sendTransaction.wait(); @@ -592,20 +571,17 @@ describe("BlsSigner", () => { // Arrange const spy = chai.spy.on(Experimental.BlsProvider.prototype, "call"); - // eslint-disable-next-line camelcase const testERC20 = MockERC20__factory.connect( networkConfig.addresses.testToken, blsProvider, ); - const transaction = { + // Act + const result = await blsSigner.call({ to: testERC20.address, data: testERC20.interface.encodeFunctionData("totalSupply"), // Explicitly omit 'from' - }; - - // Act - const result = await blsSigner.call(transaction); + }); // Assert expect(formatEther(result)).to.equal("1000000.0"); @@ -621,14 +597,14 @@ describe("BlsSigner", () => { // Arrange const spy = chai.spy.on(Experimental.BlsProvider.prototype, "estimateGas"); const recipient = ethers.Wallet.createRandom().address; - const transaction = { - to: recipient, - value: parseEther("1"), - // Explicitly omit 'from' - }; // Act - const gasEstimate = async () => await blsSigner.estimateGas(transaction); + const gasEstimate = async () => + await blsSigner.estimateGas({ + to: recipient, + value: parseEther("1"), + // Explicitly omit 'from' + }); // Assert await expect(gasEstimate()).to.not.be.rejected; @@ -753,13 +729,12 @@ describe("JsonRpcSigner", () => { // Arrange const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - to: recipient, - value: transactionAmount, - }; // Act - const result = wallet.checkTransaction(transaction); + const result = wallet.checkTransaction({ + to: recipient, + value: transactionAmount, + }); // Assert const resolvedResult = await resolveProperties(result); @@ -776,13 +751,12 @@ describe("JsonRpcSigner", () => { // Arrange const recipient = ethers.Wallet.createRandom().address; const transactionAmount = parseEther("1"); - const transaction = { - to: recipient, - value: transactionAmount, - }; // Act - const result = await wallet.populateTransaction(transaction); + const result = await wallet.populateTransaction({ + to: recipient, + value: transactionAmount, + }); // Assert expect(result).to.be.an("object").that.includes({ diff --git a/contracts/test-integration/BlsSignerContractInteraction.test.ts b/contracts/test-integration/BlsSignerContractInteraction.test.ts index 17c1a0e55..e67bc5edd 100644 --- a/contracts/test-integration/BlsSignerContractInteraction.test.ts +++ b/contracts/test-integration/BlsSignerContractInteraction.test.ts @@ -1,6 +1,7 @@ import { expect } from "chai"; import { ethers } from "hardhat"; import { BigNumber, utils, Wallet } from "ethers"; +import sinon from "sinon"; import { Experimental, BlsWalletWrapper } from "../clients/src"; import getNetworkConfig from "../shared/helpers/getNetworkConfig"; @@ -86,7 +87,10 @@ describe("Signer contract interaction tests", function () { // TODO: Add Contract deployment support #182 it("deploying contract using BlsSigner fails", async () => { + // Arrange const NewMockERC20 = await ethers.getContractFactory("MockERC20"); + + // Act const deployNewMockERC20 = async () => await NewMockERC20.connect(blsSigners[0]).deploy( "AnyToken", @@ -94,6 +98,7 @@ describe("Signer contract interaction tests", function () { tokenSupply, ); + // Assert await expect(deployNewMockERC20()).to.be.rejectedWith( TypeError, "Transaction.to should be defined", @@ -101,32 +106,34 @@ describe("Signer contract interaction tests", function () { }); it("calls balanceOf successfully after instantiating Contract class with BlsSigner", async () => { + // Arrange const blsSignerAddress = await blsSigners[0].getAddress(); const ERC20 = new ethers.Contract( mockERC20.address, mockERC20.interface, blsSigners[0], ); - expect(ERC20.signer).to.equal(blsSigners[0]); + // Act const initialBalance = await ERC20.balanceOf(blsSignerAddress); + + // Assert + expect(ERC20.signer).to.equal(blsSigners[0]); expect(initialBalance).to.equal(tokenSupply); }); it("transfer() call", async () => { + // Arrange const recipient = await blsSigners[1].getAddress(); const initialBalance = await mockERC20.balanceOf(recipient); - const fee = mockERC20 - .connect(blsSigners[0]) - .estimateGas.transfer(recipient, tokenSupply.div(2)); - await expect(fee).to.not.be.rejected; - + // Act const tx = await mockERC20 .connect(blsSigners[0]) .transfer(recipient, tokenSupply.div(2)); await tx.wait(); + // Assert const newReceipientBalance = await mockERC20.balanceOf(recipient); expect(newReceipientBalance.sub(initialBalance)).to.equal( tokenSupply.div(2), @@ -134,6 +141,7 @@ describe("Signer contract interaction tests", function () { }); it("calls transfer() successfully after instantiating Contract class with BlsSigner", async () => { + // Arrange const ERC20 = new ethers.Contract( mockERC20.address, mockERC20.interface, @@ -143,12 +151,11 @@ describe("Signer contract interaction tests", function () { const initialBalance = await mockERC20.balanceOf(recipient); const erc20ToTransfer = utils.parseEther("53.2134222"); - const fee = ERC20.estimateGas.transfer(recipient, erc20ToTransfer); - await expect(fee).to.not.be.rejected; - + // Act const tx = await ERC20.transfer(recipient, erc20ToTransfer); await tx.wait(); + // Assert const newReceipientBalance = await mockERC20.balanceOf(recipient); expect(newReceipientBalance.sub(initialBalance)).to.equal( erc20ToTransfer, @@ -156,54 +163,109 @@ describe("Signer contract interaction tests", function () { }); it("approve() and transferFrom() calls", async () => { + // Arrange const owner = await blsSigners[0].getAddress(); const spender = await blsSigners[1].getAddress(); const initialBalance = await mockERC20.balanceOf(spender); const erc20ToTransfer = utils.parseEther("11.0"); - const approveFee = mockERC20 - .connect(blsSigners[0]) - .estimateGas.approve(spender, erc20ToTransfer); - await expect(approveFee).to.not.be.rejected; - + // Act const txApprove = await mockERC20 .connect(blsSigners[0]) .approve(spender, erc20ToTransfer); await txApprove.wait(); - const transferFee = mockERC20 - .connect(blsSigners[1]) - .estimateGas.transferFrom(owner, spender, erc20ToTransfer); - await expect(transferFee).to.not.be.rejected; - const txTransferFrom = await mockERC20 .connect(blsSigners[1]) .transferFrom(owner, spender, erc20ToTransfer); await txTransferFrom.wait(); + // Assert const newBalance = await mockERC20.balanceOf(spender); expect(newBalance.sub(initialBalance)).to.equal(erc20ToTransfer); }); + it("should return the expected fee", async () => { + // Arrange + const recipient = await blsSigners[1].getAddress(); + const amount = ethers.utils.parseUnits("1"); + const expectedTransaction = await mockERC20 + .connect(blsSigners[0]) + .populateTransaction.transfer(recipient, amount); + + // BlsWalletWrapper.getRandomBlsPrivateKey from "estimateGas" method results in slightly different + // fee estimates. This fake avoids this mismatch by stubbing a constant value. + sinon.replace( + BlsWalletWrapper, + "getRandomBlsPrivateKey", + sinon.fake.resolves(blsSigners[0].wallet.blsWalletSigner.privateKey), + ); + const expectedFee = await blsSigners[0].provider.estimateGas( + expectedTransaction, + ); + + // Act + const fee = await mockERC20 + .connect(blsSigners[0]) + .estimateGas.transfer(recipient, amount); + + // Assert + expect(fee).to.equal(expectedFee); + sinon.restore(); + }); + + it("should not fail when estimating gas for different scenarios", async () => { + // Arrange + const ERC20 = new ethers.Contract( + mockERC20.address, + mockERC20.interface, + blsSigners[0], + ); + const recipient = await blsSigners[1].getAddress(); + const spender = await blsSigners[2].getAddress(); + const owner = await blsSigners[3].getAddress(); + const erc20ToTransfer = utils.parseEther("1"); + + // Act + const transferFeeWithNewContractClass = ERC20.estimateGas.transfer( + recipient, + erc20ToTransfer, + ); + const approveFee = mockERC20 + .connect(blsSigners[3]) + .estimateGas.approve(spender, erc20ToTransfer); + const transferFromFee = mockERC20 + .connect(blsSigners[2]) + .estimateGas.transferFrom(owner, spender, erc20ToTransfer); + + // Assert + await expect(transferFeeWithNewContractClass).to.not.be.rejected; + await expect(approveFee).to.not.be.rejected; + await expect(transferFromFee).to.not.be.rejected; + }); + it("contract factory connects and reconnects to new signer", async () => { + // Arrange // Truncated as not required for test const mockERC20Bytecode = "0x60806040523480"; + // Act const contractFactory = new ethers.ContractFactory( mockERC20.interface, mockERC20Bytecode, blsSigners[0], ); + const newContractFactory = contractFactory.connect(blsSigners[1]); + // Assert expect(contractFactory.signer).to.equal(blsSigners[0]); - - const newContractFactory = contractFactory.connect(blsSigners[1]); expect(newContractFactory.signer).to.equal(blsSigners[1]); }); // TODO: Add Contract deployment support #182 it("deploying via new contract factory fails", async () => { + // Arrange // Taken from artifacts directory const mockERC20Bytecode = "0x60806040523480156200001157600080fd5b5060405162000dae38038062000dae83398101604081905262000034916200022b565b828260036200004483826200032c565b5060046200005382826200032c565b5050506200006833826200007160201b60201c565b5050506200041f565b6001600160a01b038216620000cc5760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f206164647265737300604482015260640160405180910390fd5b8060026000828254620000e09190620003f8565b90915550506001600160a01b038216600090815260208190526040812080548392906200010f908490620003f8565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9060200160405180910390a35050565b505050565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200018657600080fd5b81516001600160401b0380821115620001a357620001a36200015e565b604051601f8301601f19908116603f01168101908282118183101715620001ce57620001ce6200015e565b81604052838152602092508683858801011115620001eb57600080fd5b600091505b838210156200020f5785820183015181830184015290820190620001f0565b83821115620002215760008385830101525b9695505050505050565b6000806000606084860312156200024157600080fd5b83516001600160401b03808211156200025957600080fd5b620002678783880162000174565b945060208601519150808211156200027e57600080fd5b506200028d8682870162000174565b925050604084015190509250925092565b600181811c90821680620002b357607f821691505b602082108103620002d457634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156200015957600081815260208120601f850160051c81016020861015620003035750805b601f850160051c820191505b8181101562000324578281556001016200030f565b505050505050565b81516001600160401b038111156200034857620003486200015e565b62000360816200035984546200029e565b84620002da565b602080601f8311600181146200039857600084156200037f5750858301515b600019600386901b1c1916600185901b17855562000324565b600085815260208120601f198616915b82811015620003c957888601518255948401946001909101908401620003a8565b5085821015620003e85787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b600082198211156200041a57634e487b7160e01b600052601160045260246000fd5b500190565b61097f806200042f6000396000f3fe608060405234801561001057600080fd5b50600436106100a45760003560e01c806306fdde03146100a9578063095ea7b3146100c757806318160ddd146100ea57806323b872dd146100fc578063313ce5671461010f578063395093511461011e57806340c10f191461013157806370a082311461014657806395d89b411461016f578063a457c2d714610177578063a9059cbb1461018a578063dd62ed3e1461019d575b600080fd5b6100b16101b0565b6040516100be919061079d565b60405180910390f35b6100da6100d536600461080e565b610242565b60405190151581526020016100be565b6002545b6040519081526020016100be565b6100da61010a366004610838565b61025a565b604051601281526020016100be565b6100da61012c36600461080e565b61027e565b61014461013f36600461080e565b6102a0565b005b6100ee610154366004610874565b6001600160a01b031660009081526020819052604090205490565b6100b16102ae565b6100da61018536600461080e565b6102bd565b6100da61019836600461080e565b61033d565b6100ee6101ab366004610896565b61034b565b6060600380546101bf906108c9565b80601f01602080910402602001604051908101604052809291908181526020018280546101eb906108c9565b80156102385780601f1061020d57610100808354040283529160200191610238565b820191906000526020600020905b81548152906001019060200180831161021b57829003601f168201915b5050505050905090565b600033610250818585610376565b5060019392505050565b60003361026885828561049a565b610273858585610514565b506001949350505050565b600033610250818585610291838361034b565b61029b9190610903565b610376565b6102aa82826106d0565b5050565b6060600480546101bf906108c9565b600033816102cb828661034b565b9050838110156103305760405162461bcd60e51b815260206004820152602560248201527f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f77604482015264207a65726f60d81b60648201526084015b60405180910390fd5b6102738286868403610376565b600033610250818585610514565b6001600160a01b03918216600090815260016020908152604080832093909416825291909152205490565b6001600160a01b0383166103d85760405162461bcd60e51b8152602060048201526024808201527f45524332303a20617070726f76652066726f6d20746865207a65726f206164646044820152637265737360e01b6064820152608401610327565b6001600160a01b0382166104395760405162461bcd60e51b815260206004820152602260248201527f45524332303a20617070726f766520746f20746865207a65726f206164647265604482015261737360f01b6064820152608401610327565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925910160405180910390a3505050565b60006104a6848461034b565b9050600019811461050e57818110156105015760405162461bcd60e51b815260206004820152601d60248201527f45524332303a20696e73756666696369656e7420616c6c6f77616e63650000006044820152606401610327565b61050e8484848403610376565b50505050565b6001600160a01b0383166105785760405162461bcd60e51b815260206004820152602560248201527f45524332303a207472616e736665722066726f6d20746865207a65726f206164604482015264647265737360d81b6064820152608401610327565b6001600160a01b0382166105da5760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b6064820152608401610327565b6001600160a01b038316600090815260208190526040902054818110156106525760405162461bcd60e51b815260206004820152602660248201527f45524332303a207472616e7366657220616d6f756e7420657863656564732062604482015265616c616e636560d01b6064820152608401610327565b6001600160a01b03808516600090815260208190526040808220858503905591851681529081208054849290610689908490610903565b92505081905550826001600160a01b0316846001600160a01b031660008051602061092a833981519152846040516106c391815260200190565b60405180910390a361050e565b6001600160a01b0382166107265760405162461bcd60e51b815260206004820152601f60248201527f45524332303a206d696e7420746f20746865207a65726f2061646472657373006044820152606401610327565b80600260008282546107389190610903565b90915550506001600160a01b03821660009081526020819052604081208054839290610765908490610903565b90915550506040518181526001600160a01b0383169060009060008051602061092a8339815191529060200160405180910390a35050565b600060208083528351808285015260005b818110156107ca578581018301518582016040015282016107ae565b818111156107dc576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b038116811461080957600080fd5b919050565b6000806040838503121561082157600080fd5b61082a836107f2565b946020939093013593505050565b60008060006060848603121561084d57600080fd5b610856846107f2565b9250610864602085016107f2565b9150604084013590509250925092565b60006020828403121561088657600080fd5b61088f826107f2565b9392505050565b600080604083850312156108a957600080fd5b6108b2836107f2565b91506108c0602084016107f2565b90509250929050565b600181811c908216806108dd57607f821691505b6020821081036108fd57634e487b7160e01b600052602260045260246000fd5b50919050565b6000821982111561092457634e487b7160e01b600052601160045260246000fd5b50019056feddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efa2646970667358221220ab6a94396731948535204a8026eaf25f6b11f6c7e951d939bf58529e4046659f64736f6c634300080f0033"; @@ -214,11 +276,11 @@ describe("Signer contract interaction tests", function () { blsSigners[0], ); - expect(contractFactory.signer).to.equal(blsSigners[0]); - + // Act const deploy = async () => await contractFactory.deploy("AnyToken", "TOK", tokenSupply); + // Assert await expect(deploy()).to.be.rejectedWith( TypeError, "Transaction.to should be defined", @@ -258,109 +320,122 @@ describe("Signer contract interaction tests", function () { }); it("safeMint() call passes with EOA address", async () => { + // Arrange const recipient = ethers.Wallet.createRandom().address; const tokenId = 2; - const fee = mockERC721 - .connect(blsSigners[0]) - .estimateGas.safeMint(recipient, tokenId); - await expect(fee).to.not.be.rejected; - + // Act const mint = await mockERC721 .connect(blsSigners[0]) .safeMint(recipient, tokenId); await mint.wait(); + // Assert expect(await mockERC721.connect(blsSigners[1]).ownerOf(tokenId)).to.equal( recipient, ); }); it("mint() call", async () => { + // Arrange const recipient = await blsSigners[1].getAddress(); const tokenId = 3; - const fee = mockERC721 - .connect(blsSigners[0]) - .estimateGas.mint(recipient, tokenId); - await expect(fee).to.not.be.rejected; - + // Act const mint = await mockERC721 .connect(blsSigners[0]) .mint(recipient, tokenId); await mint.wait(); + // Assert expect(await mockERC721.connect(blsSigners[1]).ownerOf(tokenId)).to.equal( recipient, ); }); it("balanceOf() call", async () => { + // Arrange const recipient = await blsSigners[1].getAddress(); const initialBalance = await mockERC721.balanceOf(recipient); const tokenId = 4; + // Act const mint = await mockERC721 .connect(blsSigners[0]) .mint(recipient, tokenId); await mint.wait(); + // Assert expect( (await mockERC721.balanceOf(recipient)).sub(initialBalance), ).to.equal(1); }); it("transfer() call", async () => { + // Arrange const tokenId = 5; const owner = await blsSigners[3].getAddress(); const recipient = await blsSigners[2].getAddress(); - // Mint a token to signer[3] const mint = await mockERC721.connect(blsSigners[0]).mint(owner, tokenId); await mint.wait(); - // Check signer[3] owns the token - expect(await mockERC721.ownerOf(tokenId)).to.equal(owner); - - const fee = mockERC721 - .connect(blsSigners[3]) - .estimateGas.transferFrom(owner, recipient, tokenId); - await expect(fee).to.not.be.rejected; - - // Transfer the token from signer 3 to signer 2 + // Act const transfer = await mockERC721 .connect(blsSigners[3]) .transferFrom(owner, recipient, tokenId); await transfer.wait(); - // Check signer[2] now owns the token + // Assert expect(await mockERC721.ownerOf(tokenId)).to.equal(recipient); }); it("approve() call", async () => { + // Arrange const owner = await blsSigners[4].getAddress(); const spender = await blsSigners[1].getAddress(); - // Mint a token to signer[4] const tokenId = 6; const mint = await mockERC721 .connect(blsSigners[0]) .safeMint(owner, tokenId); await mint.wait(); - const fee = mockERC721 - .connect(blsSigners[4]) - .estimateGas.approve(spender, tokenId); - await expect(fee).to.not.be.rejected; - - // Approve the token for signer[1] address + // Act const approve = await mockERC721 .connect(blsSigners[4]) .approve(spender, tokenId); await approve.wait(); - // Check signer[1]'s address is now an approved address for the token expect(await mockERC721.getApproved(tokenId)).to.equal(spender); }); + + it("should not fail when estimating gas", async () => { + // Arrange + const recipient = await blsSigners[1].getAddress(); + const spender = await blsSigners[2].getAddress(); + const owner = await blsSigners[3].getAddress(); + const tokenId = 7; + + // Act + const safeMintFee = mockERC721 + .connect(blsSigners[0]) + .estimateGas.safeMint(recipient, tokenId); + const mintFee = mockERC721 + .connect(blsSigners[0]) + .estimateGas.mint(recipient, tokenId); + const approveFee = mockERC721 + .connect(blsSigners[1]) + .estimateGas.approve(spender, tokenId); + const transferFromFee = mockERC721 + .connect(blsSigners[2]) + .estimateGas.transferFrom(owner, recipient, tokenId); + + // Assert + await expect(safeMintFee).to.not.be.rejected; + await expect(mintFee).to.not.be.rejected; + await expect(approveFee).to.not.be.rejected; + await expect(transferFromFee).to.not.be.rejected; + }); }); });