JavaScript testing helpers for Ethereum smart contract development with assertions, event verification, balance tracking, and time manipulation.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Singleton contract deployment, state snapshots, and specialized utilities for complex testing scenarios. Essential for advanced smart contract testing workflows.
Deploy or access the ERC1820 Registry singleton contract for interface registration testing.
/**
* Deploy or get the ERC1820 Registry singleton contract
* @param {string} funder - Address that will fund the registry deployment (needs 0.08 ETH)
* @returns {Promise<ContractInstance>} - ERC1820 Registry contract instance
*/
async function singletons.ERC1820Registry(funder);Usage Examples:
const { singletons, ether, send } = require('@openzeppelin/test-helpers');
contract('ERC777Token', function ([deployer, holder]) {
beforeEach(async function () {
// Ensure the ERC1820 Registry is deployed
this.erc1820 = await singletons.ERC1820Registry(deployer);
// Now deploy ERC777 token that depends on the registry
this.token = await ERC777.new(
'MyToken',
'MTK',
[deployer], // default operators
{ from: deployer }
);
});
it('should register interface in ERC1820', async function () {
const interfaceHash = web3.utils.keccak256('ERC777Token');
// Register interface
await this.erc1820.setInterfaceImplementer(
this.token.address,
interfaceHash,
this.token.address,
{ from: deployer }
);
// Verify registration
const implementer = await this.erc1820.getInterfaceImplementer(
this.token.address,
interfaceHash
);
expect(implementer).to.equal(this.token.address);
});
});Create snapshots of blockchain state for efficient test isolation and rollback scenarios.
/**
* Create a blockchain state snapshot
* @returns {Promise<SnapshotObject>} - Snapshot object with restore method
*/
async function snapshot();
interface SnapshotObject {
/**
* Restore blockchain to the snapshot state
* @returns {Promise<void>}
*/
restore(): Promise<void>;
}Usage Examples:
const { snapshot } = require('@openzeppelin/test-helpers');
contract('StateManagement', function ([owner, user1, user2]) {
beforeEach(async function () {
this.contract = await MyContract.new({ from: owner });
// Create snapshot after initial setup
this.snapshot = await snapshot();
});
afterEach(async function () {
// Restore to initial state after each test
await this.snapshot.restore();
});
it('should modify state in isolation', async function () {
// Modify contract state
await this.contract.setValue(100, { from: user1 });
await this.contract.setOwner(user2, { from: owner });
const value = await this.contract.getValue();
const newOwner = await this.contract.owner();
expect(value).to.be.bignumber.equal(new BN('100'));
expect(newOwner).to.equal(user2);
// State will be restored in afterEach
});
it('should start with clean state', async function () {
// This test runs with restored state
const value = await this.contract.getValue();
const owner = await this.contract.owner();
expect(value).to.be.bignumber.equal(new BN('0'));
expect(owner).to.equal(owner);
});
});const { snapshot, time } = require('@openzeppelin/test-helpers');
contract('TimeBasedContract', function ([owner]) {
beforeEach(async function () {
this.contract = await TimeBasedContract.new({ from: owner });
});
describe('time-sensitive operations', function () {
let timeSnapshot;
beforeEach(async function () {
// Create snapshot before time manipulation
timeSnapshot = await snapshot();
});
afterEach(async function () {
// Restore time state
await timeSnapshot.restore();
});
it('should work at different time periods', async function () {
// Test at current time
await this.contract.timeBasedFunction();
// Advance time and test again
await time.increase(time.duration.days(30));
await this.contract.timeBasedFunction();
// Time and state will be restored after test
});
it('should handle multiple time jumps', async function () {
const results = [];
for (let i = 0; i < 5; i++) {
await time.increase(time.duration.days(7));
const result = await this.contract.getWeeklyValue();
results.push(result);
}
// Verify progression over time
expect(results[4]).to.be.bignumber.greaterThan(results[0]);
});
});
});const { snapshot } = require('@openzeppelin/test-helpers');
contract('MultiStepProcess', function ([owner, user]) {
it('should handle complex multi-step scenarios', async function () {
// Initial setup
await this.contract.initialize(owner);
const initialSnapshot = await snapshot();
// Step 1: Configure
await this.contract.configure(someParameters);
const configuredSnapshot = await snapshot();
// Test configuration
const config = await this.contract.getConfiguration();
expect(config.param1).to.equal(expectedValue);
// Step 2: Process data
await this.contract.processData(testData);
// Test processing
const processed = await this.contract.getProcessedData();
expect(processed.length).to.equal(testData.length);
// Rollback to configured state
await configuredSnapshot.restore();
// Try alternative processing
await this.contract.processDataAlternative(alternativeData);
// Test alternative result
const alternative = await this.contract.getProcessedData();
expect(alternative.length).to.equal(alternativeData.length);
// Rollback to initial state
await initialSnapshot.restore();
// Verify complete reset
const resetConfig = await this.contract.getConfiguration();
expect(resetConfig.param1).to.equal(defaultValue);
});
});The library automatically detects and configures for different environments:
// Truffle environment - uses truffle contracts
// Hardhat environment - uses web3 contracts
// Standalone - uses web3 contracts
// Manual configuration if needed
const configure = require('@openzeppelin/test-helpers/configure');
configure({
provider: web3.currentProvider,
singletons: {
abstraction: 'web3', // or 'truffle'
defaultGas: 200000,
defaultSender: accounts[0],
}
});The ERC1820 Registry deployment follows the official EIP-1820 specification:
0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 (same on all chains)// Registry ABI methods available
const registry = await singletons.ERC1820Registry(funder);
// Set interface implementer
await registry.setInterfaceImplementer(addr, interfaceHash, implementer);
// Get interface implementer
const implementer = await registry.getInterfaceImplementer(addr, interfaceHash);
// Get manager for address
const manager = await registry.getManager(addr);
// Set manager for address
await registry.setManager(addr, newManager);Snapshots are more efficient than resetting state manually:
// Efficient - uses blockchain snapshot
beforeEach(async function () {
await this.snapshot.restore();
});
// Inefficient - redeploys contracts
beforeEach(async function () {
this.contract = await MyContract.new();
await this.contract.initialize();
await this.contract.setupInitialState();
});The ERC1820 Registry is deployed once per test session and reused:
// First call deploys registry (expensive)
const registry1 = await singletons.ERC1820Registry(funder);
// Subsequent calls return existing registry (fast)
const registry2 = await singletons.ERC1820Registry(funder);
// registry1.address === registry2.addressconst { singletons, snapshot } = require('@openzeppelin/test-helpers');
// Handle insufficient funds for registry deployment
try {
const registry = await singletons.ERC1820Registry(poorAccount);
} catch (error) {
console.error('Cannot deploy registry:', error.message);
// Fund the account or use a different funder
}
// Handle snapshot restore failures
try {
await this.snapshot.restore();
} catch (error) {
console.error('Snapshot restore failed:', error.message);
// Create new snapshot or handle failure
}const { singletons, expectEvent, ether } = require('@openzeppelin/test-helpers');
contract('ERC777Integration', function ([deployer, holder, operator]) {
beforeEach(async function () {
// Deploy registry first
this.erc1820 = await singletons.ERC1820Registry(deployer);
// Deploy ERC777 token
this.token = await ERC777.new(
'Test Token',
'TEST',
[operator],
{ from: deployer }
);
// Create snapshot for test isolation
this.snapshot = await snapshot();
});
afterEach(async function () {
await this.snapshot.restore();
});
it('should register and use token recipient', async function () {
// Deploy token recipient contract
this.recipient = await TokenRecipient.new({ from: holder });
// Register recipient interface
const recipientHash = web3.utils.keccak256('ERC777TokensRecipient');
await this.erc1820.setInterfaceImplementer(
holder,
recipientHash,
this.recipient.address,
{ from: holder }
);
// Send tokens - should trigger recipient
const receipt = await this.token.send(
holder,
ether('1'),
'0x',
{ from: deployer }
);
// Verify recipient was called
expectEvent.inTransaction(
receipt.tx,
this.recipient,
'TokensReceived'
);
});
});Install with Tessl CLI
npx tessl i tessl/npm-openzeppelin--test-helpers