0
# Advanced Features
1
2
Singleton contract deployment, state snapshots, and specialized utilities for complex testing scenarios. Essential for advanced smart contract testing workflows.
3
4
## Capabilities
5
6
### ERC1820 Registry Singleton
7
8
Deploy or access the ERC1820 Registry singleton contract for interface registration testing.
9
10
```javascript { .api }
11
/**
12
* Deploy or get the ERC1820 Registry singleton contract
13
* @param {string} funder - Address that will fund the registry deployment (needs 0.08 ETH)
14
* @returns {Promise<ContractInstance>} - ERC1820 Registry contract instance
15
*/
16
async function singletons.ERC1820Registry(funder);
17
```
18
19
**Usage Examples:**
20
21
```javascript
22
const { singletons, ether, send } = require('@openzeppelin/test-helpers');
23
24
contract('ERC777Token', function ([deployer, holder]) {
25
beforeEach(async function () {
26
// Ensure the ERC1820 Registry is deployed
27
this.erc1820 = await singletons.ERC1820Registry(deployer);
28
29
// Now deploy ERC777 token that depends on the registry
30
this.token = await ERC777.new(
31
'MyToken',
32
'MTK',
33
[deployer], // default operators
34
{ from: deployer }
35
);
36
});
37
38
it('should register interface in ERC1820', async function () {
39
const interfaceHash = web3.utils.keccak256('ERC777Token');
40
41
// Register interface
42
await this.erc1820.setInterfaceImplementer(
43
this.token.address,
44
interfaceHash,
45
this.token.address,
46
{ from: deployer }
47
);
48
49
// Verify registration
50
const implementer = await this.erc1820.getInterfaceImplementer(
51
this.token.address,
52
interfaceHash
53
);
54
expect(implementer).to.equal(this.token.address);
55
});
56
});
57
```
58
59
### Blockchain State Snapshots
60
61
Create snapshots of blockchain state for efficient test isolation and rollback scenarios.
62
63
```javascript { .api }
64
/**
65
* Create a blockchain state snapshot
66
* @returns {Promise<SnapshotObject>} - Snapshot object with restore method
67
*/
68
async function snapshot();
69
70
interface SnapshotObject {
71
/**
72
* Restore blockchain to the snapshot state
73
* @returns {Promise<void>}
74
*/
75
restore(): Promise<void>;
76
}
77
```
78
79
**Usage Examples:**
80
81
```javascript
82
const { snapshot } = require('@openzeppelin/test-helpers');
83
84
contract('StateManagement', function ([owner, user1, user2]) {
85
beforeEach(async function () {
86
this.contract = await MyContract.new({ from: owner });
87
88
// Create snapshot after initial setup
89
this.snapshot = await snapshot();
90
});
91
92
afterEach(async function () {
93
// Restore to initial state after each test
94
await this.snapshot.restore();
95
});
96
97
it('should modify state in isolation', async function () {
98
// Modify contract state
99
await this.contract.setValue(100, { from: user1 });
100
await this.contract.setOwner(user2, { from: owner });
101
102
const value = await this.contract.getValue();
103
const newOwner = await this.contract.owner();
104
105
expect(value).to.be.bignumber.equal(new BN('100'));
106
expect(newOwner).to.equal(user2);
107
108
// State will be restored in afterEach
109
});
110
111
it('should start with clean state', async function () {
112
// This test runs with restored state
113
const value = await this.contract.getValue();
114
const owner = await this.contract.owner();
115
116
expect(value).to.be.bignumber.equal(new BN('0'));
117
expect(owner).to.equal(owner);
118
});
119
});
120
```
121
122
### Advanced Snapshot Patterns
123
124
```javascript
125
const { snapshot, time } = require('@openzeppelin/test-helpers');
126
127
contract('TimeBasedContract', function ([owner]) {
128
beforeEach(async function () {
129
this.contract = await TimeBasedContract.new({ from: owner });
130
});
131
132
describe('time-sensitive operations', function () {
133
let timeSnapshot;
134
135
beforeEach(async function () {
136
// Create snapshot before time manipulation
137
timeSnapshot = await snapshot();
138
});
139
140
afterEach(async function () {
141
// Restore time state
142
await timeSnapshot.restore();
143
});
144
145
it('should work at different time periods', async function () {
146
// Test at current time
147
await this.contract.timeBasedFunction();
148
149
// Advance time and test again
150
await time.increase(time.duration.days(30));
151
await this.contract.timeBasedFunction();
152
153
// Time and state will be restored after test
154
});
155
156
it('should handle multiple time jumps', async function () {
157
const results = [];
158
159
for (let i = 0; i < 5; i++) {
160
await time.increase(time.duration.days(7));
161
const result = await this.contract.getWeeklyValue();
162
results.push(result);
163
}
164
165
// Verify progression over time
166
expect(results[4]).to.be.bignumber.greaterThan(results[0]);
167
});
168
});
169
});
170
```
171
172
### Manual Snapshot Management
173
174
```javascript
175
const { snapshot } = require('@openzeppelin/test-helpers');
176
177
contract('MultiStepProcess', function ([owner, user]) {
178
it('should handle complex multi-step scenarios', async function () {
179
// Initial setup
180
await this.contract.initialize(owner);
181
const initialSnapshot = await snapshot();
182
183
// Step 1: Configure
184
await this.contract.configure(someParameters);
185
const configuredSnapshot = await snapshot();
186
187
// Test configuration
188
const config = await this.contract.getConfiguration();
189
expect(config.param1).to.equal(expectedValue);
190
191
// Step 2: Process data
192
await this.contract.processData(testData);
193
194
// Test processing
195
const processed = await this.contract.getProcessedData();
196
expect(processed.length).to.equal(testData.length);
197
198
// Rollback to configured state
199
await configuredSnapshot.restore();
200
201
// Try alternative processing
202
await this.contract.processDataAlternative(alternativeData);
203
204
// Test alternative result
205
const alternative = await this.contract.getProcessedData();
206
expect(alternative.length).to.equal(alternativeData.length);
207
208
// Rollback to initial state
209
await initialSnapshot.restore();
210
211
// Verify complete reset
212
const resetConfig = await this.contract.getConfiguration();
213
expect(resetConfig.param1).to.equal(defaultValue);
214
});
215
});
216
```
217
218
## Configuration and Environment
219
220
### Automatic Configuration
221
222
The library automatically detects and configures for different environments:
223
224
```javascript
225
// Truffle environment - uses truffle contracts
226
// Hardhat environment - uses web3 contracts
227
// Standalone - uses web3 contracts
228
229
// Manual configuration if needed
230
const configure = require('@openzeppelin/test-helpers/configure');
231
configure({
232
provider: web3.currentProvider,
233
singletons: {
234
abstraction: 'web3', // or 'truffle'
235
defaultGas: 200000,
236
defaultSender: accounts[0],
237
}
238
});
239
```
240
241
### ERC1820 Registry Details
242
243
The ERC1820 Registry deployment follows the official EIP-1820 specification:
244
245
- **Address**: `0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24` (same on all chains)
246
- **Deployment Cost**: 0.08 ETH (transferred to deployment account)
247
- **Deployment Method**: Pre-signed transaction broadcast
248
249
```javascript
250
// Registry ABI methods available
251
const registry = await singletons.ERC1820Registry(funder);
252
253
// Set interface implementer
254
await registry.setInterfaceImplementer(addr, interfaceHash, implementer);
255
256
// Get interface implementer
257
const implementer = await registry.getInterfaceImplementer(addr, interfaceHash);
258
259
// Get manager for address
260
const manager = await registry.getManager(addr);
261
262
// Set manager for address
263
await registry.setManager(addr, newManager);
264
```
265
266
## Performance Considerations
267
268
### Snapshot Efficiency
269
270
Snapshots are more efficient than resetting state manually:
271
272
```javascript
273
// Efficient - uses blockchain snapshot
274
beforeEach(async function () {
275
await this.snapshot.restore();
276
});
277
278
// Inefficient - redeploys contracts
279
beforeEach(async function () {
280
this.contract = await MyContract.new();
281
await this.contract.initialize();
282
await this.contract.setupInitialState();
283
});
284
```
285
286
### Registry Deployment
287
288
The ERC1820 Registry is deployed once per test session and reused:
289
290
```javascript
291
// First call deploys registry (expensive)
292
const registry1 = await singletons.ERC1820Registry(funder);
293
294
// Subsequent calls return existing registry (fast)
295
const registry2 = await singletons.ERC1820Registry(funder);
296
297
// registry1.address === registry2.address
298
```
299
300
## Error Handling
301
302
```javascript
303
const { singletons, snapshot } = require('@openzeppelin/test-helpers');
304
305
// Handle insufficient funds for registry deployment
306
try {
307
const registry = await singletons.ERC1820Registry(poorAccount);
308
} catch (error) {
309
console.error('Cannot deploy registry:', error.message);
310
// Fund the account or use a different funder
311
}
312
313
// Handle snapshot restore failures
314
try {
315
await this.snapshot.restore();
316
} catch (error) {
317
console.error('Snapshot restore failed:', error.message);
318
// Create new snapshot or handle failure
319
}
320
```
321
322
## Integration Examples
323
324
### Testing ERC777 with Registry
325
326
```javascript
327
const { singletons, expectEvent, ether } = require('@openzeppelin/test-helpers');
328
329
contract('ERC777Integration', function ([deployer, holder, operator]) {
330
beforeEach(async function () {
331
// Deploy registry first
332
this.erc1820 = await singletons.ERC1820Registry(deployer);
333
334
// Deploy ERC777 token
335
this.token = await ERC777.new(
336
'Test Token',
337
'TEST',
338
[operator],
339
{ from: deployer }
340
);
341
342
// Create snapshot for test isolation
343
this.snapshot = await snapshot();
344
});
345
346
afterEach(async function () {
347
await this.snapshot.restore();
348
});
349
350
it('should register and use token recipient', async function () {
351
// Deploy token recipient contract
352
this.recipient = await TokenRecipient.new({ from: holder });
353
354
// Register recipient interface
355
const recipientHash = web3.utils.keccak256('ERC777TokensRecipient');
356
await this.erc1820.setInterfaceImplementer(
357
holder,
358
recipientHash,
359
this.recipient.address,
360
{ from: holder }
361
);
362
363
// Send tokens - should trigger recipient
364
const receipt = await this.token.send(
365
holder,
366
ether('1'),
367
'0x',
368
{ from: deployer }
369
);
370
371
// Verify recipient was called
372
expectEvent.inTransaction(
373
receipt.tx,
374
this.recipient,
375
'TokensReceived'
376
);
377
});
378
});
379
```