0
# Transaction Management
1
2
Control transaction mempool state for testing transaction lifecycle scenarios, including transaction dropping and mempool manipulation.
3
4
## Capabilities
5
6
### Drop Transaction
7
8
Removes a transaction from the mempool, useful for testing transaction failure scenarios and mempool behavior.
9
10
```typescript { .api }
11
/**
12
* Removes the given transaction from the mempool, if it exists
13
* @param txHash Transaction hash of the transaction to be removed from the mempool
14
* @returns Promise resolving to true if transaction was successfully removed, false if not found
15
*/
16
dropTransaction(txHash: string): Promise<boolean>;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { network } from "hardhat";
23
24
const { networkHelpers } = await network.connect();
25
const [signer] = await ethers.getSigners();
26
27
// Send a transaction but don't wait for it to be mined
28
const tx = await signer.sendTransaction({
29
to: "0x123...",
30
value: ethers.parseEther("1"),
31
gasLimit: 21000
32
});
33
34
console.log(`Transaction hash: ${tx.hash}`);
35
36
// Remove transaction from mempool before it gets mined
37
const success = await networkHelpers.dropTransaction(tx.hash);
38
console.log(`Transaction dropped: ${success}`);
39
40
// Try to drop non-existent transaction
41
const notFound = await networkHelpers.dropTransaction("0xnonexistent123...");
42
console.log(`Non-existent transaction dropped: ${notFound}`); // false
43
```
44
45
## Transaction Testing Patterns
46
47
### Testing Transaction Failures
48
49
```typescript
50
it("should handle dropped transactions", async () => {
51
const { networkHelpers } = await network.connect();
52
const [signer] = await ethers.getSigners();
53
54
// Send transaction with low gas price (might not get mined quickly)
55
const tx = await signer.sendTransaction({
56
to: "0x123...",
57
value: ethers.parseEther("1"),
58
gasPrice: 1 // Very low gas price
59
});
60
61
// Drop the transaction from mempool
62
const dropped = await networkHelpers.dropTransaction(tx.hash);
63
expect(dropped).to.be.true;
64
65
// Transaction should not be found in mempool anymore
66
const txFromMempool = await ethers.provider.getTransaction(tx.hash);
67
expect(txFromMempool).to.be.null;
68
69
// Mining blocks shouldn't include the dropped transaction
70
await networkHelpers.mine(5);
71
72
const receipt = await ethers.provider.getTransactionReceipt(tx.hash);
73
expect(receipt).to.be.null;
74
});
75
```
76
77
### Testing Mempool Management
78
79
```typescript
80
it("should manage mempool effectively", async () => {
81
const { networkHelpers } = await network.connect();
82
const [signer] = await ethers.getSigners();
83
84
// Send multiple transactions
85
const txHashes: string[] = [];
86
87
for (let i = 0; i < 5; i++) {
88
const tx = await signer.sendTransaction({
89
to: "0x123...",
90
value: ethers.parseEther("0.1"),
91
nonce: await signer.getNonce() + i
92
});
93
txHashes.push(tx.hash);
94
}
95
96
// Drop some transactions selectively
97
await networkHelpers.dropTransaction(txHashes[1]);
98
await networkHelpers.dropTransaction(txHashes[3]);
99
100
// Mine blocks
101
await networkHelpers.mine(2);
102
103
// Check which transactions were mined
104
for (let i = 0; i < txHashes.length; i++) {
105
const receipt = await ethers.provider.getTransactionReceipt(txHashes[i]);
106
107
if (i === 1 || i === 3) {
108
// These were dropped
109
expect(receipt).to.be.null;
110
} else {
111
// These should be mined
112
expect(receipt).to.not.be.null;
113
expect(receipt.status).to.equal(1);
114
}
115
}
116
});
117
```
118
119
### Testing Transaction Replacement
120
121
```typescript
122
it("should handle transaction replacement scenarios", async () => {
123
const { networkHelpers } = await network.connect();
124
const [signer] = await ethers.getSigners();
125
126
const nonce = await signer.getNonce();
127
128
// Send original transaction with low gas price
129
const originalTx = await signer.sendTransaction({
130
to: "0x123...",
131
value: ethers.parseEther("1"),
132
gasPrice: ethers.parseUnits("1", "gwei"),
133
nonce: nonce
134
});
135
136
// Drop the original transaction
137
await networkHelpers.dropTransaction(originalTx.hash);
138
139
// Send replacement transaction with higher gas price (same nonce)
140
const replacementTx = await signer.sendTransaction({
141
to: "0x456...",
142
value: ethers.parseEther("2"),
143
gasPrice: ethers.parseUnits("10", "gwei"),
144
nonce: nonce // Same nonce as original
145
});
146
147
// Mine the replacement
148
await networkHelpers.mine();
149
150
// Original should not be mined
151
const originalReceipt = await ethers.provider.getTransactionReceipt(originalTx.hash);
152
expect(originalReceipt).to.be.null;
153
154
// Replacement should be mined
155
const replacementReceipt = await ethers.provider.getTransactionReceipt(replacementTx.hash);
156
expect(replacementReceipt).to.not.be.null;
157
expect(replacementReceipt.to).to.equal("0x456...");
158
});
159
```
160
161
### Testing Contract Interaction Failures
162
163
```typescript
164
it("should test contract interaction with dropped transactions", async () => {
165
const { networkHelpers } = await network.connect();
166
167
const counter = await ethers.deployContract("Counter");
168
const [signer] = await ethers.getSigners();
169
170
// Send contract interaction transaction
171
const tx = await counter.increment();
172
173
// Drop the transaction before it's mined
174
await networkHelpers.dropTransaction(tx.hash);
175
176
// Mine some blocks
177
await networkHelpers.mine(3);
178
179
// Counter should not have been incremented
180
expect(await counter.count()).to.equal(0);
181
182
// Send another increment transaction (this one won't be dropped)
183
await counter.increment();
184
185
// Now counter should be incremented
186
expect(await counter.count()).to.equal(1);
187
});
188
```
189
190
### Testing Transaction Pool Limits
191
192
```typescript
193
it("should test mempool behavior under stress", async () => {
194
const { networkHelpers } = await network.connect();
195
const [signer] = await ethers.getSigners();
196
197
const txHashes: string[] = [];
198
const nonce = await signer.getNonce();
199
200
// Fill mempool with many transactions
201
for (let i = 0; i < 100; i++) {
202
const tx = await signer.sendTransaction({
203
to: "0x123...",
204
value: ethers.parseEther("0.001"),
205
gasPrice: ethers.parseUnits("1", "gwei"),
206
nonce: nonce + i
207
});
208
txHashes.push(tx.hash);
209
}
210
211
// Drop every other transaction
212
const droppedCount = await Promise.all(
213
txHashes
214
.filter((_, i) => i % 2 === 0)
215
.map(hash => networkHelpers.dropTransaction(hash))
216
);
217
218
const successfulDrops = droppedCount.filter(success => success).length;
219
expect(successfulDrops).to.be.greaterThan(0);
220
221
// Mine all remaining transactions
222
await networkHelpers.mine(10);
223
224
// Verify approximately half the transactions were mined
225
const minedCount = await Promise.all(
226
txHashes.map(async hash => {
227
const receipt = await ethers.provider.getTransactionReceipt(hash);
228
return receipt !== null;
229
})
230
);
231
232
const actualMinedCount = minedCount.filter(mined => mined).length;
233
expect(actualMinedCount).to.be.approximately(50, 10); // Allow some variance
234
});
235
```
236
237
### Testing Time-Sensitive Transactions
238
239
```typescript
240
it("should test time-sensitive transaction dropping", async () => {
241
const { networkHelpers } = await network.connect();
242
243
// Deploy time-sensitive contract (e.g., auction with deadline)
244
const deadline = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
245
const auction = await ethers.deployContract("TimedAuction", [deadline]);
246
247
const [bidder1, bidder2] = await ethers.getSigners();
248
249
// Place bid near deadline
250
await networkHelpers.time.increaseTo(deadline - 60); // 1 minute before deadline
251
252
const bidTx = await auction.connect(bidder1).bid({ value: ethers.parseEther("1") });
253
254
// Drop the bid transaction
255
await networkHelpers.dropTransaction(bidTx.hash);
256
257
// Advance past deadline
258
await networkHelpers.time.increaseTo(deadline + 1);
259
260
// Mine blocks
261
await networkHelpers.mine();
262
263
// Verify auction ended without the dropped bid
264
expect(await auction.ended()).to.be.true;
265
expect(await auction.highestBidder()).to.equal(ethers.ZeroAddress);
266
267
// Late bid should fail (auction ended)
268
await expect(
269
auction.connect(bidder2).bid({ value: ethers.parseEther("2") })
270
).to.be.revertedWith("Auction ended");
271
});
272
```
273
274
## Transaction Lifecycle Testing
275
276
### Complete Transaction Lifecycle
277
278
```typescript
279
it("should test complete transaction lifecycle", async () => {
280
const { networkHelpers } = await network.connect();
281
const [signer] = await ethers.getSigners();
282
283
// Create transaction
284
const tx = await signer.sendTransaction({
285
to: "0x123...",
286
value: ethers.parseEther("1")
287
});
288
289
// Transaction should be in mempool
290
const pendingTx = await ethers.provider.getTransaction(tx.hash);
291
expect(pendingTx).to.not.be.null;
292
expect(pendingTx.blockNumber).to.be.null; // Not mined yet
293
294
// Drop transaction
295
const dropped = await networkHelpers.dropTransaction(tx.hash);
296
expect(dropped).to.be.true;
297
298
// Transaction should no longer be in mempool
299
const droppedTx = await ethers.provider.getTransaction(tx.hash);
300
expect(droppedTx).to.be.null;
301
302
// Mine blocks
303
await networkHelpers.mine(5);
304
305
// Transaction should never appear in any block
306
const receipt = await ethers.provider.getTransactionReceipt(tx.hash);
307
expect(receipt).to.be.null;
308
});
309
```