0
# Storage Operations
1
2
Direct access to contract storage slots enables advanced testing scenarios including state manipulation, storage layout verification, and testing edge cases that are difficult to reach through normal contract interfaces.
3
4
## Capabilities
5
6
### Get Storage At
7
8
Reads data from a specific storage slot of a contract.
9
10
```typescript { .api }
11
/**
12
* Retrieves the data located at the given address, storage index, and block number
13
* @param address The contract address to retrieve storage from
14
* @param index The storage slot position (0-based)
15
* @param block The block number, or "latest", "earliest", or "pending" (defaults to "latest")
16
* @returns Promise resolving to a string containing the hexadecimal value stored at that slot
17
*/
18
getStorageAt(
19
address: string,
20
index: NumberLike,
21
block?: NumberLike | BlockTag
22
): Promise<string>;
23
24
type BlockTag = "latest" | "earliest" | "pending";
25
```
26
27
**Usage Examples:**
28
29
```typescript
30
import { network } from "hardhat";
31
32
const { networkHelpers } = await network.connect();
33
34
// Read storage slot 0 (often the first state variable)
35
const slot0 = await networkHelpers.getStorageAt("0x123...", 0);
36
console.log(`Storage slot 0: ${slot0}`);
37
38
// Read specific slot at a specific block
39
const historicalValue = await networkHelpers.getStorageAt(
40
"0x123...",
41
5,
42
1000 // block number
43
);
44
45
// Read using different block tags
46
const latestValue = await networkHelpers.getStorageAt("0x123...", 0, "latest");
47
const pendingValue = await networkHelpers.getStorageAt("0x123...", 0, "pending");
48
```
49
50
### Set Storage At
51
52
Writes data directly to a specific storage slot of a contract.
53
54
```typescript { .api }
55
/**
56
* Writes a single value to an account's storage at the specified slot
57
* @param address The contract address where the value should be stored
58
* @param index The storage slot position (0-based)
59
* @param value The value to store (will be converted to 32-byte hex)
60
* @returns Promise that resolves once the storage value is set
61
*/
62
setStorageAt(
63
address: string,
64
index: NumberLike,
65
value: NumberLike
66
): Promise<void>;
67
```
68
69
**Usage Examples:**
70
71
```typescript
72
const { networkHelpers } = await network.connect();
73
74
// Set storage slot 0 to value 42
75
await networkHelpers.setStorageAt("0x123...", 0, 42);
76
77
// Set storage slot with hex value
78
await networkHelpers.setStorageAt("0x123...", 1, "0x1234567890abcdef");
79
80
// Set storage slot with bigint
81
await networkHelpers.setStorageAt("0x123...", 2, 1000000000000000000n);
82
```
83
84
### Set Code
85
86
Replaces the bytecode of a contract, effectively changing its implementation.
87
88
```typescript { .api }
89
/**
90
* Modifies the bytecode stored at an account's address
91
* @param address The address where the given code should be stored
92
* @param code The bytecode to store (as a hex string with 0x prefix)
93
* @returns Promise that resolves once the code is set
94
*/
95
setCode(address: string, code: string): Promise<void>;
96
```
97
98
**Usage Examples:**
99
100
```typescript
101
const { networkHelpers } = await network.connect();
102
103
// Deploy new contract and get its bytecode
104
const newContract = await ethers.deployContract("NewImplementation");
105
const newBytecode = await ethers.provider.getCode(newContract.target);
106
107
// Replace existing contract's bytecode
108
await networkHelpers.setCode("0x123...", newBytecode);
109
110
// Set empty bytecode (effectively deleting the contract)
111
await networkHelpers.setCode("0x456...", "0x");
112
```
113
114
## Storage Testing Patterns
115
116
### Direct State Manipulation
117
118
```typescript
119
it("should manipulate contract state directly", async () => {
120
const { networkHelpers } = await network.connect();
121
122
const contract = await ethers.deployContract("Counter");
123
const contractAddress = await contract.getAddress();
124
125
// Read initial value (assuming counter is in slot 0)
126
const initialValue = await networkHelpers.getStorageAt(contractAddress, 0);
127
expect(parseInt(initialValue, 16)).to.equal(0);
128
129
// Set counter to 100 directly via storage
130
await networkHelpers.setStorageAt(contractAddress, 0, 100);
131
132
// Verify through contract interface
133
expect(await contract.count()).to.equal(100);
134
});
135
```
136
137
### Testing Storage Layout
138
139
```typescript
140
it("should verify storage layout", async () => {
141
const { networkHelpers } = await network.connect();
142
143
// Contract with multiple state variables:
144
// uint256 public value1; // slot 0
145
// uint256 public value2; // slot 1
146
// address public owner; // slot 2
147
const contract = await ethers.deployContract("MultiStorage");
148
const contractAddress = await contract.getAddress();
149
150
// Set values through contract
151
await contract.setValue1(123);
152
await contract.setValue2(456);
153
154
// Verify storage layout
155
const slot0 = await networkHelpers.getStorageAt(contractAddress, 0);
156
const slot1 = await networkHelpers.getStorageAt(contractAddress, 1);
157
158
expect(parseInt(slot0, 16)).to.equal(123);
159
expect(parseInt(slot1, 16)).to.equal(456);
160
161
// Owner should be in slot 2
162
const slot2 = await networkHelpers.getStorageAt(contractAddress, 2);
163
const owner = await contract.owner();
164
expect(slot2.toLowerCase()).to.include(owner.slice(2).toLowerCase());
165
});
166
```
167
168
### Proxy Contract Testing
169
170
```typescript
171
it("should test proxy implementation switching", async () => {
172
const { networkHelpers } = await network.connect();
173
174
// Deploy proxy and implementations
175
const proxy = await ethers.deployContract("Proxy");
176
const impl1 = await ethers.deployContract("Implementation1");
177
const impl2 = await ethers.deployContract("Implementation2");
178
179
const proxyAddress = await proxy.getAddress();
180
181
// Set initial implementation
182
await proxy.setImplementation(impl1.target);
183
184
// Get implementation bytecode
185
const impl2Code = await ethers.provider.getCode(impl2.target);
186
187
// Replace proxy's implementation directly
188
await networkHelpers.setCode(proxyAddress, impl2Code);
189
190
// Verify the proxy now behaves like Implementation2
191
const proxyAsImpl2 = await ethers.getContractAt("Implementation2", proxyAddress);
192
await expect(proxyAsImpl2.newFunction()).to.not.be.reverted;
193
});
194
```
195
196
### Simulating Storage Corruption
197
198
```typescript
199
it("should handle corrupted storage", async () => {
200
const { networkHelpers } = await network.connect();
201
202
const contract = await ethers.deployContract("TokenContract");
203
const contractAddress = await contract.getAddress();
204
205
// Corrupt the total supply (assuming it's in slot 0)
206
await networkHelpers.setStorageAt(contractAddress, 0, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff");
207
208
// Contract should handle overflow/corruption gracefully
209
await expect(contract.transfer("0x123...", 1)).to.be.reverted;
210
});
211
```
212
213
### Bypassing Access Controls
214
215
```typescript
216
it("should bypass access controls via storage", async () => {
217
const { networkHelpers } = await network.connect();
218
219
const contract = await ethers.deployContract("AccessControlled");
220
const contractAddress = await contract.getAddress();
221
222
// Find the storage slot for the admin address (assume slot 0)
223
const myAddress = await ethers.getSigners().then(s => s[0].address);
224
225
// Set ourselves as admin directly via storage
226
await networkHelpers.setStorageAt(
227
contractAddress,
228
0,
229
ethers.zeroPadValue(myAddress, 32)
230
);
231
232
// Now admin functions should work
233
await expect(contract.adminOnlyFunction()).to.not.be.reverted;
234
});
235
```
236
237
### Testing Packed Storage
238
239
```typescript
240
it("should handle packed storage variables", async () => {
241
const { networkHelpers } = await network.connect();
242
243
// Contract with packed variables:
244
// struct PackedData {
245
// uint128 value1; // slot 0, first 16 bytes
246
// uint128 value2; // slot 0, last 16 bytes
247
// }
248
const contract = await ethers.deployContract("PackedStorage");
249
const contractAddress = await contract.getAddress();
250
251
// Set packed values - both values in same slot
252
const value1 = 123;
253
const value2 = 456;
254
255
// Pack both values into single 32-byte slot
256
const packedValue = ethers.toBigInt(value1) | (ethers.toBigInt(value2) << 128n);
257
258
await networkHelpers.setStorageAt(contractAddress, 0, packedValue);
259
260
// Verify both values are correctly stored
261
expect(await contract.getValue1()).to.equal(value1);
262
expect(await contract.getValue2()).to.equal(value2);
263
});
264
```
265
266
### Historical State Analysis
267
268
```typescript
269
it("should analyze historical state", async () => {
270
const { networkHelpers } = await network.connect();
271
272
const contract = await ethers.deployContract("HistoryContract");
273
const contractAddress = await contract.getAddress();
274
275
// Record initial state
276
const initialBlock = await networkHelpers.time.latestBlock();
277
await contract.setValue(100);
278
279
// Change state
280
await networkHelpers.mine(5);
281
await contract.setValue(200);
282
283
// Change state again
284
await networkHelpers.mine(5);
285
await contract.setValue(300);
286
287
const finalBlock = await networkHelpers.time.latestBlock();
288
289
// Read historical values
290
const valueAtInitial = await networkHelpers.getStorageAt(
291
contractAddress,
292
0,
293
initialBlock + 1
294
);
295
const valueAtMiddle = await networkHelpers.getStorageAt(
296
contractAddress,
297
0,
298
initialBlock + 6
299
);
300
const valueAtEnd = await networkHelpers.getStorageAt(
301
contractAddress,
302
0,
303
finalBlock
304
);
305
306
expect(parseInt(valueAtInitial, 16)).to.equal(100);
307
expect(parseInt(valueAtMiddle, 16)).to.equal(200);
308
expect(parseInt(valueAtEnd, 16)).to.equal(300);
309
});
310
```