or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdbalance-tracking.mdconstants-utilities.mdevent-testing.mdindex.mdrevert-testing.mdtime-manipulation.md

advanced-features.mddocs/

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

```