Files
bls-wallet/contracts/test-integration/BlsSigner.test.ts
jacque006 dc6ebc24d6 Fix contract tests
Fix issues in contract tests from merge conflict resolution with main.
Add expect utility for public keys to prevent false positive failures when hex values are zero padded (0x00...).
Replace most pubkey deep equality checks with new util.
2023-03-29 18:14:55 -06:00

1156 lines
33 KiB
TypeScript

/* eslint-disable camelcase */
import chai, { expect } from "chai";
import { ethers, BigNumber } from "ethers";
import {
parseEther,
resolveProperties,
RLP,
formatEther,
} from "ethers/lib/utils";
import sinon from "sinon";
import {
Experimental,
ActionData,
BlsWalletWrapper,
NetworkConfig,
MockERC20Factory,
Operation,
} from "../clients/src";
import getNetworkConfig from "../shared/helpers/getNetworkConfig";
import addSafetyPremiumToFee from "../clients/src/helpers/addSafetyDivisorToFee";
let networkConfig: NetworkConfig;
let aggregatorUrl: string;
let verificationGateway: string;
let aggregatorUtilities: string;
let rpcUrl: string;
let network: ethers.providers.Networkish;
let privateKey: string;
let blsProvider: InstanceType<typeof Experimental.BlsProvider>;
let blsSigner: InstanceType<typeof Experimental.BlsSigner>;
let regularProvider: ethers.providers.JsonRpcProvider;
let fundedWallet: ethers.Wallet;
describe("BlsSigner", () => {
beforeEach(async () => {
networkConfig = await getNetworkConfig("local");
aggregatorUrl = "http://localhost:3000";
verificationGateway = networkConfig.addresses.verificationGateway;
aggregatorUtilities = networkConfig.addresses.utilities;
rpcUrl = "http://localhost:8545";
network = {
name: "localhost",
chainId: 0x539, // 1337
};
privateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
blsProvider = new Experimental.BlsProvider(
aggregatorUrl,
verificationGateway,
aggregatorUtilities,
rpcUrl,
network,
);
blsSigner = blsProvider.getSigner(privateKey);
regularProvider = new ethers.providers.JsonRpcProvider(rpcUrl);
fundedWallet = new ethers.Wallet(
"0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a", // Hardhat Account #2 private key
regularProvider,
);
await fundedWallet.sendTransaction({
to: await blsSigner.getAddress(),
value: parseEther("10"),
});
});
it("should send ETH (empty call) successfully", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const expectedBalance = parseEther("1");
const recipientBalanceBefore = await blsProvider.getBalance(recipient);
// Act
const transaction = await blsSigner.sendTransaction({
to: recipient,
value: expectedBalance,
});
await transaction.wait();
// Assert
expect(
(await blsProvider.getBalance(recipient)).sub(recipientBalanceBefore),
).to.equal(expectedBalance);
});
it("should throw an error when sending an invalid transaction", async () => {
// Arrange
const invalidValue = parseEther("-1");
// Act
const result = async () =>
await blsSigner.sendTransaction({
to: ethers.Wallet.createRandom().address,
value: invalidValue,
});
// Assert
await expect(result()).to.be.rejectedWith(
Error,
'invalid BigNumber value (argument="value", value=undefined, code=INVALID_ARGUMENT, version=bignumber/5.7.0)',
);
});
it("should return a transaction response when sending a transaction", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const transactionAmount = parseEther("1");
const expectedNonce = await BlsWalletWrapper.Nonce(
blsSigner.wallet.PublicKey(),
blsSigner.verificationGatewayAddress,
blsProvider,
);
// Act
const transactionResponse = await blsSigner.sendTransaction({
to: recipient,
value: transactionAmount,
data: "0x",
});
// Assert
expect(transactionResponse).to.be.an("object").that.includes({
hash: transactionResponse.hash,
to: recipient,
from: blsSigner.wallet.address,
data: "0x",
chainId: 1337,
type: 2,
confirmations: 1,
});
expect(transactionResponse.nonce).to.equal(expectedNonce);
expect(transactionResponse.gasLimit).to.equal(BigNumber.from("0x0"));
expect(transactionResponse.value).to.equal(
BigNumber.from(transactionAmount),
);
});
it("should send a batch of ETH transfers (empty calls) successfully", async () => {
// Arrange
const expectedAmount = parseEther("1");
const recipients = [];
const transactionBatch = [];
for (let i = 0; i < 3; i++) {
recipients.push(ethers.Wallet.createRandom().address);
transactionBatch.push({
to: recipients[i],
value: expectedAmount,
});
}
// Act
const transaction = await blsSigner.sendTransactionBatch({
transactions: transactionBatch,
});
await transaction.awaitBatchReceipt();
// Assert
expect(await blsProvider.getBalance(recipients[0])).to.equal(
expectedAmount,
);
expect(await blsProvider.getBalance(recipients[1])).to.equal(
expectedAmount,
);
expect(await blsProvider.getBalance(recipients[2])).to.equal(
expectedAmount,
);
});
it("should not retrieve nonce when sending a transaction batch with batch options", async () => {
// Arrange
const spy = chai.spy.on(BlsWalletWrapper, "Nonce");
const recipient = ethers.Wallet.createRandom().address;
const expectedAmount = parseEther("1");
const transactionBatch = {
transactions: [
{
to: recipient,
value: expectedAmount,
},
],
batchOptions: {
gas: ethers.utils.parseUnits("40000"),
maxPriorityFeePerGas: ethers.utils.parseUnits("0.5", "gwei"),
maxFeePerGas: ethers.utils.parseUnits("23", "gwei"),
nonce: 0,
chainId: 1337,
accessList: [],
},
};
// Act
const transaction = await blsSigner.sendTransactionBatch(transactionBatch);
await transaction.awaitBatchReceipt();
// Assert
expect(await blsProvider.getBalance(recipient)).to.equal(expectedAmount);
// Nonce is supplied with batch options so spy should not be called
expect(spy).to.have.been.called.exactly(0);
chai.spy.restore(spy);
});
it("should retrieve nonce when sending a transaction batch without batch options", async () => {
// Arrange
const spy = chai.spy.on(BlsWalletWrapper, "Nonce");
const recipient = ethers.Wallet.createRandom().address;
const expectedAmount = parseEther("1");
// Act
const transaction = await blsSigner.sendTransactionBatch({
transactions: [
{
to: recipient,
value: expectedAmount,
},
],
});
await transaction.awaitBatchReceipt();
// Assert
expect(await blsProvider.getBalance(recipient)).to.equal(expectedAmount);
// Nonce is not supplied with batch options so spy should be called once in sendTransactionBatch
expect(spy).to.have.been.called.exactly(1);
chai.spy.restore(spy);
});
it("should throw an error sending & signing a transaction batch when 'transaction.to' has not been defined", async () => {
// Arrange
const transactionBatch = new Array(3).fill({
...{ value: parseEther("1") },
});
// Act
const sendResult = async () =>
await blsSigner.sendTransactionBatch({
transactions: transactionBatch,
});
const signResult = async () =>
await blsSigner.signTransactionBatch({
transactions: transactionBatch,
});
// Assert
await expect(sendResult()).to.be.rejectedWith(
TypeError,
"Transaction.to is missing on transaction 0",
);
await expect(signResult()).to.be.rejectedWith(
TypeError,
"Transaction.to is missing on transaction 0",
);
});
it("should throw an error when sending an invalid transaction batch", async () => {
// Arrange
const invalidTransactionBatch = new Array(3).fill({
...{
to: ethers.Wallet.createRandom().address,
value: parseEther("-1"),
},
});
// Act
const result = async () =>
await blsSigner.sendTransactionBatch({
transactions: invalidTransactionBatch,
});
// Assert
await expect(result()).to.be.rejectedWith(
Error,
'invalid BigNumber value (argument="value", value=undefined, code=INVALID_ARGUMENT, version=bignumber/5.7.0)',
);
});
it("should return a transaction batch response when sending a transaction batch", async () => {
// Arrange
const expectedAmount = parseEther("1");
const recipients = [];
const transactionBatch = [];
const expectedNonce = await BlsWalletWrapper.Nonce(
blsSigner.wallet.PublicKey(),
blsSigner.verificationGatewayAddress,
blsProvider,
);
for (let i = 0; i < 3; i++) {
recipients.push(ethers.Wallet.createRandom().address);
transactionBatch.push({
to: recipients[i],
value: expectedAmount,
});
}
// Act
const transactionBatchResponse = await blsSigner.sendTransactionBatch({
transactions: transactionBatch,
});
// Assert
// tx 1
expect(transactionBatchResponse.transactions[0])
.to.be.an("object")
.that.includes({
hash: transactionBatchResponse.transactions[0].hash,
to: recipients[0],
from: blsSigner.wallet.address,
data: "0x",
chainId: 1337,
type: 2,
confirmations: 1,
});
expect(transactionBatchResponse.transactions[0].nonce).to.equal(
expectedNonce,
);
expect(transactionBatchResponse.transactions[0].gasLimit).to.equal(
BigNumber.from("0x0"),
);
expect(transactionBatchResponse.transactions[0].value).to.equal(
BigNumber.from(expectedAmount),
);
// tx 2
expect(transactionBatchResponse.transactions[1])
.to.be.an("object")
.that.includes({
hash: transactionBatchResponse.transactions[1].hash,
to: recipients[1],
from: blsSigner.wallet.address,
data: "0x",
chainId: 1337,
type: 2,
confirmations: 1,
});
expect(transactionBatchResponse.transactions[1].nonce).to.equal(
expectedNonce,
);
expect(transactionBatchResponse.transactions[1].gasLimit).to.equal(
BigNumber.from("0x0"),
);
expect(transactionBatchResponse.transactions[1].value).to.equal(
BigNumber.from(expectedAmount),
);
// tx 3
expect(transactionBatchResponse.transactions[2])
.to.be.an("object")
.that.includes({
hash: transactionBatchResponse.transactions[2].hash,
to: recipients[2],
from: blsSigner.wallet.address,
data: "0x",
chainId: 1337,
type: 2,
confirmations: 1,
});
expect(transactionBatchResponse.transactions[2].nonce).to.equal(
expectedNonce,
);
expect(transactionBatchResponse.transactions[2].gasLimit).to.equal(
BigNumber.from("0x0"),
);
expect(transactionBatchResponse.transactions[2].value).to.equal(
BigNumber.from(expectedAmount),
);
});
it("should validate batch options", async () => {
// Arrange
const batchOptions = {
gas: ethers.utils.parseUnits("40000"),
maxPriorityFeePerGas: ethers.utils.parseUnits("0.5", "gwei"),
maxFeePerGas: ethers.utils.parseUnits("23", "gwei"),
nonce: 39,
chainId: 1337,
accessList: [],
};
// Act
const result = await blsSigner._validateBatchOptions(batchOptions);
// Assert
expect(result).to.deep.equal(batchOptions);
expect(result.nonce).to.equal(BigNumber.from("39"));
expect(result.nonce).to.have.property("add");
expect(result.nonce).to.not.be.a("number");
});
it("should throw error for wrong chain id when validating batch options", async () => {
// Arrange
const invalidChainId = 123;
const batchOptions = {
gas: BigNumber.from("40000"),
maxPriorityFeePerGas: ethers.utils.parseUnits("0.5", "gwei"),
maxFeePerGas: ethers.utils.parseUnits("23", "gwei"),
nonce: 1,
chainId: invalidChainId,
accessList: [],
};
// Act
const result = async () =>
await blsSigner._validateBatchOptions(batchOptions);
// Assert
expect(result()).to.be.rejectedWith(
Error,
`Supplied chain ID ${invalidChainId} does not match the expected chain ID 1337`,
);
});
it("should throw an error when invalid private key is supplied", async () => {
// Arrange
const newBlsProvider = new Experimental.BlsProvider(
aggregatorUrl,
verificationGateway,
aggregatorUtilities,
rpcUrl,
network,
);
const newBlsSigner = newBlsProvider.getSigner("invalidPrivateKey");
// Act
const result = async () =>
await newBlsSigner.sendTransaction({
to: ethers.Wallet.createRandom().address,
value: parseEther("1"),
});
// Assert
await expect(result()).to.be.rejectedWith(
Error,
"Expect hex but got invalidPrivateKey",
);
});
it("should throw an error when invalid private key is supplied after a valid getSigner call", async () => {
// Arrange
const newBlsSigner = blsProvider.getSigner("invalidPrivateKey");
// Act
const result = async () =>
await newBlsSigner.sendTransaction({
to: ethers.Wallet.createRandom().address,
value: parseEther("1"),
});
// Assert
await expect(result()).to.be.rejectedWith(
Error,
"Expect hex but got invalidPrivateKey",
);
});
it("should retrieve the account address", async () => {
// Arrange
const expectedAddress = await BlsWalletWrapper.Address(
privateKey,
verificationGateway,
blsProvider,
);
// Act
const address = await blsSigner.getAddress();
// Assert
expect(address).to.equal(expectedAddress);
});
it("should sign a transaction to create a bundleDto and serialize the result", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const transaction = {
value: "1000000000000000000",
to: recipient,
data: "0x",
from: await blsSigner.getAddress(),
};
// get expected signature
const expectedAction: ActionData = {
ethValue: parseEther("1"),
contractAddress: recipient,
encodedFunction: "0x",
};
const wallet = await BlsWalletWrapper.connect(
privateKey,
verificationGateway,
blsProvider,
);
const walletAddress = wallet.address;
const expectedNonce = await BlsWalletWrapper.Nonce(
wallet.PublicKey(),
verificationGateway,
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",
sinon.fake.resolves(privateKey),
);
const expectedFeeEstimate = await blsProvider.estimateGas(transaction);
const actionsWithSafeFee = blsProvider._addFeePaymentActionWithSafeFee(
[expectedAction],
expectedFeeEstimate,
);
const expectedOperation = {
nonce: expectedNonce,
gas: BigNumber.from(30_000_000),
actions: [...actionsWithSafeFee],
};
const expectedBundle = wallet.blsWalletSigner.sign(
expectedOperation,
walletAddress,
);
const expectedBundleSignatureHexStrings = expectedBundle.signature.map(
(keyElement) => BigNumber.from(keyElement).toHexString(),
);
// Act
const signedTransaction = await blsSigner.signTransaction(transaction);
// Assert
const bundleDto = JSON.parse(signedTransaction);
expect(bundleDto.signature).to.deep.equal(
expectedBundleSignatureHexStrings,
);
sinon.restore();
});
it("should throw an error when signing an invalid transaction", async () => {
// Arrange
const invalidEthValue = parseEther("-1");
// Act
const result = async () =>
await blsSigner.signTransaction({
value: invalidEthValue,
to: ethers.Wallet.createRandom().address,
});
// Assert
await expect(result()).to.be.rejectedWith(
Error,
'invalid BigNumber value (argument="value", value=undefined, code=INVALID_ARGUMENT, version=bignumber/5.7.0)',
);
});
it("should sign a transaction batch to create a bundleDto and serialize the result", async () => {
// Arrange
const expectedAmount = parseEther("1");
const recipients = [];
const transactions = [];
const expectedActions = [];
for (let i = 0; i < 3; i++) {
recipients.push(ethers.Wallet.createRandom().address);
transactions.push({
to: recipients[i],
value: expectedAmount,
data: "0x",
});
expectedActions.push({
contractAddress: recipients[i],
ethValue: expectedAmount,
encodedFunction: "0x",
});
}
// get expected signature
const wallet = await BlsWalletWrapper.connect(
privateKey,
verificationGateway,
blsProvider,
);
const walletAddress = wallet.address;
const expectedNonce = await BlsWalletWrapper.Nonce(
wallet.PublicKey(),
verificationGateway,
blsSigner,
);
const actionsWithFeePaymentAction =
blsProvider._addFeePaymentActionForFeeEstimation(expectedActions);
const expectedFeeEstimate = await blsProvider.aggregator.estimateFee(
blsSigner.wallet.sign({
nonce: expectedNonce,
gas: BigNumber.from(30_000_000),
actions: [...actionsWithFeePaymentAction],
}),
);
const safeFee = addSafetyPremiumToFee(
BigNumber.from(expectedFeeEstimate.feeRequired),
);
const actionsWithSafeFee = blsProvider._addFeePaymentActionWithSafeFee(
expectedActions,
safeFee,
);
const op: Omit<Operation, "gas"> = {
nonce: expectedNonce,
actions: [...actionsWithSafeFee],
};
const gas = await wallet.estimateGas(op);
const expectedBundle = wallet.blsWalletSigner.sign(
{ ...op, gas },
walletAddress,
);
// Act
const signedTransaction = await blsSigner.signTransactionBatch({
transactions,
});
// Assert
const bundleDto = JSON.parse(signedTransaction);
expect(bundleDto.signature).to.deep.equal(expectedBundle.signature);
});
it("should throw an error when signing an invalid transaction batch", async () => {
// Arrange
const invalidEthValue = parseEther("-1");
const unsignedTransaction = {
value: invalidEthValue,
to: ethers.Wallet.createRandom().address,
};
// Act
const result = async () =>
await blsSigner.signTransactionBatch({
transactions: [unsignedTransaction],
});
// Assert
await expect(result()).to.be.rejectedWith(
Error,
'invalid BigNumber value (argument="value", value=undefined, code=INVALID_ARGUMENT, version=bignumber/5.7.0)',
);
});
it("should not retrieve nonce when signing a transaction batch with batch options", async () => {
// Arrange
const spy = chai.spy.on(BlsWalletWrapper, "Nonce");
const transactionBatch = {
transactions: [
{
to: ethers.Wallet.createRandom().address,
value: parseEther("1"),
},
],
batchOptions: {
gas: ethers.utils.parseUnits("40000"),
maxPriorityFeePerGas: ethers.utils.parseUnits("0.5", "gwei"),
maxFeePerGas: ethers.utils.parseUnits("23", "gwei"),
nonce: 0,
chainId: 1337,
accessList: [],
},
};
// Act
await blsSigner.signTransactionBatch(transactionBatch);
// Assert
// Nonce is supplied with batch options so spy should not be called
expect(spy).to.have.been.called.exactly(0);
chai.spy.restore(spy);
});
it("should retrieve nonce when signing a transaction batch without batch options", async () => {
// Arrange
const spy = chai.spy.on(BlsWalletWrapper, "Nonce");
// Act
await blsSigner.signTransactionBatch({
transactions: [
{
to: ethers.Wallet.createRandom().address,
value: parseEther("1"),
},
],
});
// Assert
// Nonce is not supplied with batch options so spy should be called once in sendTransactionBatch
expect(spy).to.have.been.called.exactly(1);
chai.spy.restore(spy);
});
it("should check transaction", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const transactionAmount = parseEther("1");
// Act
const result = blsSigner.checkTransaction({
to: recipient,
value: transactionAmount,
});
// Assert
const resolvedResult = await resolveProperties(result);
expect(resolvedResult).to.be.an("object").that.includes({
to: recipient,
value: transactionAmount,
from: blsSigner.wallet.address,
});
});
// TODO: This tests a non-overrideen method and seems to pull the nonce from the aggregator instance.
// So will revisit this and ensure the method is using the correct nonce at a later stage.
it("should populate transaction", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const transactionAmount = parseEther("1");
// Act
const result = await blsSigner.populateTransaction({
to: recipient,
value: transactionAmount,
});
// Assert
expect(result).to.be.an("object").that.includes({
to: recipient,
value: transactionAmount,
from: blsSigner.wallet.address,
type: 2,
chainId: 1337,
});
expect(result).to.include.keys(
"maxFeePerGas",
"maxPriorityFeePerGas",
"gasLimit",
"nonce",
);
});
it("should sign message of type string", async () => {
// Arrange
const address = ethers.Wallet.createRandom().address;
const blsWalletSignerSignature =
blsSigner.wallet.blsWalletSigner.signMessage(address);
const expectedSignature = RLP.encode(blsWalletSignerSignature);
// Act
const signedMessage = await blsSigner.signMessage(address);
// Assert
expect(signedMessage).to.deep.equal(expectedSignature);
expect(RLP.decode(signedMessage)).to.deep.equal(blsWalletSignerSignature);
});
it("should sign message of type bytes", async () => {
// Arrange
const bytes: number[] = [68, 219, 115, 219, 26, 248, 170, 165]; // random bytes
const hexString = ethers.utils.hexlify(bytes);
const blsWalletSignerSignature =
blsSigner.wallet.blsWalletSigner.signMessage(hexString);
const expectedSignature = RLP.encode(blsWalletSignerSignature);
// Act
const signedMessage = await blsSigner.signMessage(bytes);
// Assert
expect(signedMessage).to.deep.equal(expectedSignature);
expect(RLP.decode(signedMessage)).to.deep.equal(blsWalletSignerSignature);
});
it("should await the init promise when connecting to an unchecked bls signer", async () => {
// Arrange
const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
const newBlsSigner = blsProvider.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 balanceBefore = await blsProvider.getBalance(recipient);
// Act
const uncheckedResponse = await uncheckedBlsSigner.sendTransaction({
value: transactionAmount,
to: recipient,
});
await uncheckedResponse.wait();
// Assert
expect(
(await blsProvider.getBalance(recipient)).sub(balanceBefore),
).to.equal(transactionAmount);
});
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,
verificationGateway,
aggregatorUtilities,
rpcUrl,
network,
);
const newPrivateKey = await BlsWalletWrapper.getRandomBlsPrivateKey();
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 balanceBefore = await blsProvider.getBalance(recipient);
// Act
const uncheckedResponse = await uncheckedBlsSigner.sendTransaction({
value: transactionAmount,
to: recipient,
});
await uncheckedResponse.wait();
// Assert
expect(
(await blsProvider.getBalance(recipient)).sub(balanceBefore),
).to.equal(transactionAmount);
});
it("should send ETH (empty call) via an unchecked transaction", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const transactionAmount = parseEther("1");
const balanceBefore = await blsProvider.getBalance(recipient);
// Act
const uncheckedTransactionHash = await blsSigner.sendUncheckedTransaction({
value: transactionAmount,
to: recipient,
});
await blsProvider.getTransactionReceipt(uncheckedTransactionHash);
// Assert
expect(
(await blsProvider.getBalance(recipient)).sub(balanceBefore),
).to.equal(transactionAmount);
});
it("should send ETH (empty call) using an unchecked bls signer", async () => {
// Arrange
const uncheckedBlsSigner = blsSigner.connectUnchecked();
const recipient = ethers.Wallet.createRandom().address;
const transactionAmount = parseEther("1");
const balanceBefore = await blsProvider.getBalance(recipient);
// Act
const uncheckedResponse = await uncheckedBlsSigner.sendTransaction({
value: transactionAmount,
to: recipient,
});
await uncheckedResponse.wait();
// Assert
expect(uncheckedResponse).to.be.an("object").that.includes({
hash: uncheckedResponse.hash,
data: "",
chainId: 0,
confirmations: 0,
from: "",
});
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),
).to.equal(transactionAmount);
});
it("should get the balance of an account", async () => {
// Arrange
const expectedBalance = await regularProvider.getBalance(
blsSigner.wallet.address,
);
// Act
const result = await blsSigner.getBalance();
// Assert
expect(result).to.equal(expectedBalance);
});
it("should get the balance of an account at specific block height", async () => {
// Arrange
const expectedBalance = parseEther("0");
// Act
const result = await blsSigner.getBalance("earliest");
// Assert
expect(result).to.equal(expectedBalance);
});
it("should get the chain id the wallet is connected to", async () => {
// Arrange
const { chainId: expectedChainId } = await regularProvider.getNetwork();
// Act
const result = await blsSigner.getChainId();
// Assert
expect(result).to.equal(expectedChainId);
});
it("should get the current gas price", async () => {
// Arrange
const expectedGasPrice = await regularProvider.getGasPrice();
// Act
const result = await blsSigner.getGasPrice();
// Assert
expect(result).to.equal(expectedGasPrice);
});
it("should get the number of transactions the account has sent", async () => {
// Arrange
const expectedTransactionCount = await blsSigner.wallet.Nonce();
// Act
const transactionCount = await blsSigner.getTransactionCount();
// Assert
expect(transactionCount).to.equal(expectedTransactionCount);
});
it("should get the number of transactions the account has sent at the specified block tag", async () => {
// Arrange
const expectedTransactionCount = 0;
const sendTransaction = await blsSigner.sendTransaction({
value: parseEther("1"),
to: ethers.Wallet.createRandom().address,
});
await sendTransaction.wait();
// Act
const transactionCount = await blsSigner.getTransactionCount("earliest");
// Assert
expect(transactionCount).to.equal(expectedTransactionCount);
});
it("should return the result of call using the transactionRequest, with the signer account address being used as the from field", async () => {
// Arrange
const spy = chai.spy.on(Experimental.BlsProvider.prototype, "call");
const testERC20 = MockERC20Factory.connect(
networkConfig.addresses.testToken,
blsProvider,
);
// Act
const result = await blsSigner.call({
to: testERC20.address,
data: testERC20.interface.encodeFunctionData("totalSupply"),
// Explicitly omit 'from'
});
// Assert
expect(formatEther(result)).to.equal("1000000.0");
expect(spy).to.have.been.called.once;
expect(spy).to.have.been.called.with({
to: testERC20.address,
data: testERC20.interface.encodeFunctionData("totalSupply"),
from: blsSigner.wallet.address, // Assert that 'from' has been added to the provider call
});
chai.spy.restore(spy);
});
it("should estimate gas without throwing an error, with the signer account address being used as the from field.", async () => {
// Arrange
const spy = chai.spy.on(Experimental.BlsProvider.prototype, "estimateGas");
const recipient = ethers.Wallet.createRandom().address;
// Act
const gasEstimate = async () =>
await blsSigner.estimateGas({
to: recipient,
value: parseEther("1"),
// Explicitly omit 'from'
});
// Assert
await expect(gasEstimate()).to.not.be.rejected;
expect(spy).to.have.been.called.once;
expect(spy).to.have.been.called.with({
to: recipient,
value: parseEther("1"),
from: blsSigner.wallet.address, // Assert that 'from' has been added to the provider call
});
chai.spy.restore(spy);
});
// ENS is not supported by hardhat so we are checking the correct error behaviour in this scenario
it("should throw an error when passing in a correct ENS name", async () => {
// Arrange
const ensName = "vitalik.eth";
// Act
const result = async () => await blsSigner.resolveName(ensName);
// Assert
await expect(result()).to.be.rejectedWith(
Error,
"network does not support ENS",
);
});
it("should throw an error when trying to unlock an account with http access", async () => {
// Arrange
const madeUpPassword = "password";
// Act
const unlock = async () => await blsSigner.unlock(madeUpPassword);
// Assert
await expect(unlock()).to.be.rejectedWith(
Error,
"account unlock with HTTP access is forbidden",
);
});
it("should validate a transaction request", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const getBalancePromise = blsSigner.getBalance();
const expectedValidatedTransaction = {
to: recipient,
value: await blsSigner.getBalance(),
from: await blsSigner.getAddress(),
};
// Act
const validatedTransaction = await blsSigner._validateTransaction({
to: recipient,
value: getBalancePromise,
});
// Assert
expect(validatedTransaction).to.deep.equal(expectedValidatedTransaction);
});
it("should throw an error validating a transaction request when transaction.to is not defined", async () => {
// Arrange & Act
const result = async () =>
await blsSigner._validateTransaction({
value: await blsSigner.getBalance(),
});
// Assert
await expect(result()).to.be.rejectedWith(
TypeError,
"Transaction.to should be defined",
);
});
it("should validate a transaction batch", async () => {
// Arrange
const recipient = ethers.Wallet.createRandom().address;
const amount = await blsSigner.getBalance();
const expectedValidatedTransactionBatch = {
transactions: [
{
to: recipient,
value: amount,
from: await blsSigner.getAddress(),
},
],
batchOptions: undefined,
};
// Act
const validatedTransaction = await blsSigner._validateTransactionBatch({
transactions: [
{
to: recipient,
value: amount,
},
],
});
// Assert
expect(validatedTransaction).to.deep.equal(
expectedValidatedTransactionBatch,
);
});
it("should throw an error validating a transaction batch when transaction.to is not defined", async () => {
// Arrange & Act
const result = async () =>
await blsSigner._validateTransactionBatch({
transactions: [
{
value: await blsSigner.getBalance(),
},
],
});
// Assert
await expect(result()).to.be.rejectedWith(
TypeError,
"Transaction.to is missing on transaction 0",
);
});
});