From c8e5d00f509dab18cefbd694c1a8c8ec52984b04 Mon Sep 17 00:00:00 2001 From: Beau Rancourt Date: Tue, 30 Jan 2024 10:52:52 -0500 Subject: [PATCH] Add a `waitForTransaction` to time helpers The [hardhat-ethers](https://github.com/NomicFoundation/hardhat/blob/8f47553a88eb1251907398513449bc15689041d6/packages/hardhat-ethers/src/internal/hardhat-ethers-provider.ts#L362-L368) version is unimplemented, so we provide one. --- src/index.ts | 2 +- src/time.ts | 34 +++++++++++++++++++++++++++- test/time.test.ts | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 78e394c..d7e30f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -53,7 +53,7 @@ extendEnvironment((hre) => { return ownable(hre) }), time: lazyObject(() => { - return time() + return time(hre) }), signers: lazyObject(() => { return signers(hre) diff --git a/src/time.ts b/src/time.ts index 4042578..7f3945f 100644 --- a/src/time.ts +++ b/src/time.ts @@ -1,3 +1,4 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types" import { mine, mineUpTo, @@ -30,6 +31,8 @@ export interface HardhatTimeHelpers { * @param {number} blocks */ mineBlocksTo(blocks: number): Promise + + waitForTransaction(txHash: string, confirmations?: number): Promise } /** @@ -86,12 +89,41 @@ export async function mineBlocksTo(targetBlock: number): Promise { return await timeHelpers.latestBlock() } -export default function (): HardhatTimeHelpers { +export async function waitForTransaction( + hre: HardhatRuntimeEnvironment, + txHash: string, + confirmations: number = 1 +): Promise { + if (hre.network.name === "hardhat") { + return true + } + + const { provider } = hre.ethers + const transaction = await provider.getTransaction(txHash) + if (!transaction) { + throw new Error(`Transaction ${txHash} not found`) + } + + let currentConfirmations = await transaction.confirmations() + while (currentConfirmations < confirmations) { + // wait 1s between each check to save API compute units + // eslint-disable-next-line no-await-in-loop, no-promise-executor-return + await new Promise((resolve) => setTimeout(resolve, 1000)) + // eslint-disable-next-line no-await-in-loop + currentConfirmations = await transaction.confirmations() + } + + return true +} + +export default function (hre: HardhatRuntimeEnvironment): HardhatTimeHelpers { return { lastBlockNumber: () => lastBlockNumber(), lastBlockTime: () => lastBlockTime(), increaseTime: (time: number) => increaseTime(time), mineBlocks: (blocks: number) => mineBlocks(blocks), mineBlocksTo: (targetBlock: number) => mineBlocksTo(targetBlock), + waitForTransaction: (txHash: string, confirmations?: number) => + waitForTransaction(hre, txHash, confirmations), } } diff --git a/test/time.test.ts b/test/time.test.ts index 42be612..da34c70 100644 --- a/test/time.test.ts +++ b/test/time.test.ts @@ -1,11 +1,24 @@ import { useEnvironment } from "./helpers" import type { HardhatTimeHelpers } from "../src/time" +import type { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" +import type { HardhatEthersHelpers } from "@nomicfoundation/hardhat-ethers/types" +import type { ethers as ethersT } from "ethers" + +import { mine } from "@nomicfoundation/hardhat-network-helpers" import chai from "chai" chai.use(require("chai-as-promised")) const { expect } = chai +function timeout(ms: number) { + return new Promise((resolve) => { + setTimeout(() => { + resolve("Timed out after " + ms + " ms") + }, ms) + }) +} + describe("time helpers", function () { context("default hardhat project", function () { useEnvironment("hardhat-project") @@ -114,5 +127,49 @@ describe("time helpers", function () { ) }) }) + + describe("waitForTransaction function", function () { + let ethers: typeof ethersT & HardhatEthersHelpers + let signer: HardhatEthersSigner + let transactionResponse: ethersT.TransactionResponse + + beforeEach(async function () { + ethers = this.hre.ethers + signer = (await ethers.getSigners())[0] + + const tx = { + to: signer.address, // Sending the transaction to the signer's own address + value: ethers.parseEther("0.01"), // Small amount of Ether, can also be 0 + } + transactionResponse = await signer.sendTransaction(tx) + }) + + it("returns immediately on hardhat", async function () { + const waitResult = await Promise.race([ + timeHelpers.waitForTransaction(transactionResponse.hash, 5), + timeout(300), + ]) + expect(waitResult).to.be.true + }) + + it("does not return immediately on non-hardhat networks", async function () { + this.hre.network.name = "not-hardhat" + const waitResult = await Promise.race([ + timeHelpers.waitForTransaction(transactionResponse.hash, 5), + timeout(1000), + ]) + expect(waitResult).to.equal("Timed out after 1000 ms") + }) + + it("returns after the transaction is mined", async function () { + this.hre.network.name = "not-hardhat" + await mine(6) + const waitResult = await Promise.race([ + timeHelpers.waitForTransaction(transactionResponse.hash, 5), + timeout(1000), + ]) + expect(waitResult).to.be.true + }) + }) }) })