or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

access-control.mdcontract-presets.mdcryptography-math.mdgsn-support.mdindex.mdintrospection.mdpayment-mechanisms.mdproxy-patterns.mdsecurity-utilities.mdtoken-standards.md

introspection.mddocs/

0

# Introspection

1

2

OpenZeppelin provides interface detection and registry patterns including ERC165 for interface introspection and ERC1820 for registry-based interface implementation discovery. These utilities enable dynamic interface detection and service discovery in smart contracts.

3

4

## Capabilities

5

6

### ERC165 - Interface Detection Standard

7

8

Standard interface detection mechanism that allows contracts to declare which interfaces they support.

9

10

```solidity { .api }

11

/**

12

* Interface of the ERC165 standard as defined in the EIP

13

*/

14

interface IERC165 {

15

/**

16

* Returns true if this contract implements the interface defined by interfaceId

17

*/

18

function supportsInterface(bytes4 interfaceId) external view returns (bool);

19

}

20

21

/**

22

* Implementation of the IERC165 interface

23

*/

24

abstract contract ERC165 is IERC165 {

25

/**

26

* See IERC165.supportsInterface

27

*/

28

function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);

29

}

30

```

31

32

**Usage Example:**

33

34

```solidity

35

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

36

import "@openzeppelin/contracts/token/ERC721/IERC721.sol";

37

38

contract MyNFT is ERC165, IERC721 {

39

// ERC721 implementation...

40

41

function supportsInterface(bytes4 interfaceId)

42

public view virtual override(ERC165, IERC165) returns (bool)

43

{

44

return

45

interfaceId == type(IERC721).interfaceId ||

46

super.supportsInterface(interfaceId);

47

}

48

}

49

```

50

51

### ERC165Checker - Interface Detection Utility

52

53

Library to query support of an interface declared via ERC165 by a contract.

54

55

```solidity { .api }

56

/**

57

* Library used to query support of an interface declared via ERC165

58

*/

59

library ERC165Checker {

60

/**

61

* Returns true if contract supports the ERC165 interface

62

*/

63

function supportsERC165(address account) internal view returns (bool);

64

65

/**

66

* Returns true if contract supports the given interface

67

*/

68

function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool);

69

70

/**

71

* Returns a boolean array where each value corresponds to the interfaces passed in

72

*/

73

function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool[] memory);

74

75

/**

76

* Returns true if contract supports all the given interfaces

77

*/

78

function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool);

79

}

80

```

81

82

**Usage Example:**

83

84

```solidity

85

import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

86

87

contract InterfaceDetector {

88

using ERC165Checker for address;

89

90

function checkNFTSupport(address contractAddr) external view returns (bool) {

91

return contractAddr.supportsInterface(type(IERC721).interfaceId);

92

}

93

94

function checkMultipleInterfaces(address contractAddr) external view returns (bool) {

95

bytes4[] memory interfaces = new bytes4[](2);

96

interfaces[0] = type(IERC721).interfaceId;

97

interfaces[1] = type(IERC721Metadata).interfaceId;

98

99

return contractAddr.supportsAllInterfaces(interfaces);

100

}

101

102

function getInterfaceSupport(address contractAddr) external view returns (bool[] memory) {

103

bytes4[] memory interfaces = new bytes4[](3);

104

interfaces[0] = type(IERC20).interfaceId;

105

interfaces[1] = type(IERC721).interfaceId;

106

interfaces[2] = type(IERC1155).interfaceId;

107

108

return ERC165Checker.getSupportedInterfaces(contractAddr, interfaces);

109

}

110

}

111

```

112

113

### ERC1820Implementer - Registry Interface Implementation

114

115

Base contract for ERC1820 implementer contracts that handle interface implementations.

116

117

```solidity { .api }

118

/**

119

* Implementation of the IERC1820Implementer interface

120

*/

121

contract ERC1820Implementer is IERC1820Implementer {

122

/**

123

* Bytes32 storage slot for mapping interface name hashes to implementer addresses

124

*/

125

mapping(bytes32 => bool) private _supportedInterfaces;

126

127

/**

128

* See IERC1820Implementer.canImplementInterfaceForAddress

129

*/

130

function canImplementInterfaceForAddress(bytes32 interfaceHash, address account)

131

external view virtual override returns (bytes32);

132

133

/**

134

* Declares the contract as willing to be an implementer of interfaceHash for account

135

*/

136

function _registerInterfaceForAddress(bytes32 interfaceHash, address account) internal virtual;

137

138

/**

139

* Sets or unsets an interface for the calling account

140

*/

141

function _setInterface(string memory interfaceName, bool canImplement) internal;

142

}

143

```

144

145

### IERC1820Implementer - ERC1820 Implementer Interface

146

147

Interface for ERC1820 implementer contracts.

148

149

```solidity { .api }

150

/**

151

* Interface for an ERC1820 implementer

152

*/

153

interface IERC1820Implementer {

154

/**

155

* Returns a special value (ERC1820_ACCEPT_MAGIC) if this contract implements interfaceHash for account

156

*/

157

function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32);

158

}

159

```

160

161

### IERC1820Registry - ERC1820 Registry Interface

162

163

Interface for the global ERC1820 registry contract.

164

165

```solidity { .api }

166

/**

167

* Interface of the global ERC1820 Registry

168

*/

169

interface IERC1820Registry {

170

/**

171

* Sets newManager as the manager for account

172

*/

173

function setManager(address account, address newManager) external;

174

175

/**

176

* Returns the manager for account

177

*/

178

function getManager(address account) external view returns (address);

179

180

/**

181

* Sets the implementer contract for interfaceHash and account to implementer

182

*/

183

function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external;

184

185

/**

186

* Returns the implementer of interfaceHash for account

187

*/

188

function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address);

189

190

/**

191

* Computes the keccak256 hash of an interface name

192

*/

193

function interfaceHash(string calldata interfaceName) external pure returns (bytes32);

194

195

/**

196

* Updates the cache with whether the contract implements an ERC165 interface or not

197

*/

198

function updateERC165Cache(address account, bytes4 interfaceId) external;

199

200

/**

201

* Checks whether a contract implements an ERC165 interface or not

202

*/

203

function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);

204

205

/**

206

* Checks whether a contract implements an ERC165 interface or not without using nor updating the cache

207

*/

208

function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);

209

210

event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);

211

event ManagerChanged(address indexed account, address indexed newManager);

212

}

213

```

214

215

## Introspection Patterns

216

217

### Dynamic Interface Detection

218

219

```solidity

220

import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

221

222

contract UniversalTokenHandler {

223

using ERC165Checker for address;

224

225

bytes4 private constant IERC20_INTERFACE_ID = 0x36372b07;

226

bytes4 private constant IERC721_INTERFACE_ID = 0x80ac58cd;

227

bytes4 private constant IERC1155_INTERFACE_ID = 0xd9b67a26;

228

229

enum TokenType { UNKNOWN, ERC20, ERC721, ERC1155 }

230

231

function detectTokenType(address token) public view returns (TokenType) {

232

if (token.supportsInterface(IERC1155_INTERFACE_ID)) {

233

return TokenType.ERC1155;

234

} else if (token.supportsInterface(IERC721_INTERFACE_ID)) {

235

return TokenType.ERC721;

236

} else if (token.supportsInterface(IERC20_INTERFACE_ID)) {

237

return TokenType.ERC20;

238

} else {

239

return TokenType.UNKNOWN;

240

}

241

}

242

243

function handleTokenTransfer(

244

address token,

245

address from,

246

address to,

247

uint256 amount

248

) external {

249

TokenType tokenType = detectTokenType(token);

250

251

if (tokenType == TokenType.ERC20) {

252

IERC20(token).transferFrom(from, to, amount);

253

} else if (tokenType == TokenType.ERC721) {

254

IERC721(token).transferFrom(from, to, amount);

255

} else if (tokenType == TokenType.ERC1155) {

256

IERC1155(token).safeTransferFrom(from, to, amount, 1, "");

257

} else {

258

revert("Unsupported token type");

259

}

260

}

261

}

262

```

263

264

### Custom Interface Registration

265

266

```solidity

267

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

268

269

interface ICustomService {

270

function processData(bytes calldata data) external returns (bool);

271

function getServiceInfo() external view returns (string memory);

272

}

273

274

contract CustomServiceProvider is ERC165, ICustomService {

275

string private _serviceInfo;

276

277

constructor(string memory serviceInfo) {

278

_serviceInfo = serviceInfo;

279

}

280

281

function processData(bytes calldata data) external override returns (bool) {

282

// Service implementation

283

return true;

284

}

285

286

function getServiceInfo() external view override returns (string memory) {

287

return _serviceInfo;

288

}

289

290

function supportsInterface(bytes4 interfaceId)

291

public view virtual override returns (bool)

292

{

293

return

294

interfaceId == type(ICustomService).interfaceId ||

295

super.supportsInterface(interfaceId);

296

}

297

}

298

299

contract ServiceConsumer {

300

using ERC165Checker for address;

301

302

function useServiceIfSupported(address provider, bytes calldata data) external {

303

if (provider.supportsInterface(type(ICustomService).interfaceId)) {

304

ICustomService service = ICustomService(provider);

305

bool success = service.processData(data);

306

require(success, "Service processing failed");

307

}

308

}

309

}

310

```

311

312

### ERC1820 Registry Usage

313

314

```solidity

315

import "@openzeppelin/contracts/utils/introspection/ERC1820Implementer.sol";

316

317

// Registry address (deployed once per network)

318

IERC1820Registry constant ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);

319

320

interface IDataProcessor {

321

function processUserData(bytes calldata data) external returns (bytes memory);

322

}

323

324

contract DataProcessorImplementer is ERC1820Implementer, IDataProcessor {

325

bytes32 private constant DATA_PROCESSOR_HASH = keccak256("DataProcessor");

326

327

constructor() {

328

_setInterface("DataProcessor", true);

329

}

330

331

function processUserData(bytes calldata data) external override returns (bytes memory) {

332

// Processing logic

333

return abi.encode("Processed:", data);

334

}

335

336

function canImplementInterfaceForAddress(bytes32 interfaceHash, address account)

337

external view virtual override returns (bytes32)

338

{

339

if (interfaceHash == DATA_PROCESSOR_HASH) {

340

return ERC1820_ACCEPT_MAGIC;

341

} else {

342

return bytes32(0x00);

343

}

344

}

345

}

346

347

contract DataUser {

348

bytes32 private constant DATA_PROCESSOR_HASH = keccak256("DataProcessor");

349

350

function processData(bytes calldata data) external returns (bytes memory) {

351

address implementer = ERC1820_REGISTRY.getInterfaceImplementer(

352

address(this),

353

DATA_PROCESSOR_HASH

354

);

355

356

require(implementer != address(0), "No data processor registered");

357

358

return IDataProcessor(implementer).processUserData(data);

359

}

360

361

function setDataProcessor(address processor) external {

362

ERC1820_REGISTRY.setInterfaceImplementer(

363

address(this),

364

DATA_PROCESSOR_HASH,

365

processor

366

);

367

}

368

}

369

```

370

371

### Plugin Architecture with Interface Detection

372

373

```solidity

374

import "@openzeppelin/contracts/utils/introspection/ERC165.sol";

375

import "@openzeppelin/contracts/access/Ownable.sol";

376

377

interface IPlugin {

378

function getPluginName() external view returns (string memory);

379

function execute(bytes calldata data) external returns (bool);

380

}

381

382

contract PluginManager is Ownable {

383

using ERC165Checker for address;

384

385

address[] public plugins;

386

mapping(address => bool) public isRegistered;

387

388

event PluginRegistered(address indexed plugin, string name);

389

event PluginRemoved(address indexed plugin);

390

391

function registerPlugin(address plugin) external onlyOwner {

392

require(plugin.supportsInterface(type(IPlugin).interfaceId), "Not a valid plugin");

393

require(!isRegistered[plugin], "Plugin already registered");

394

395

plugins.push(plugin);

396

isRegistered[plugin] = true;

397

398

string memory name = IPlugin(plugin).getPluginName();

399

emit PluginRegistered(plugin, name);

400

}

401

402

function removePlugin(address plugin) external onlyOwner {

403

require(isRegistered[plugin], "Plugin not registered");

404

405

for (uint i = 0; i < plugins.length; i++) {

406

if (plugins[i] == plugin) {

407

plugins[i] = plugins[plugins.length - 1];

408

plugins.pop();

409

break;

410

}

411

}

412

413

isRegistered[plugin] = false;

414

emit PluginRemoved(plugin);

415

}

416

417

function executePlugin(address plugin, bytes calldata data) external returns (bool) {

418

require(isRegistered[plugin], "Plugin not registered");

419

return IPlugin(plugin).execute(data);

420

}

421

422

function executeAllPlugins(bytes calldata data) external returns (bool[] memory) {

423

bool[] memory results = new bool[](plugins.length);

424

425

for (uint i = 0; i < plugins.length; i++) {

426

results[i] = IPlugin(plugins[i]).execute(data);

427

}

428

429

return results;

430

}

431

432

function getPluginCount() external view returns (uint256) {

433

return plugins.length;

434

}

435

436

function getPluginInfo() external view returns (address[] memory, string[] memory) {

437

string[] memory names = new string[](plugins.length);

438

439

for (uint i = 0; i < plugins.length; i++) {

440

names[i] = IPlugin(plugins[i]).getPluginName();

441

}

442

443

return (plugins, names);

444

}

445

}

446

447

contract ExamplePlugin is ERC165, IPlugin {

448

string private _name;

449

450

constructor(string memory name) {

451

_name = name;

452

}

453

454

function getPluginName() external view override returns (string memory) {

455

return _name;

456

}

457

458

function execute(bytes calldata data) external override returns (bool) {

459

// Plugin-specific logic

460

return true;

461

}

462

463

function supportsInterface(bytes4 interfaceId)

464

public view virtual override returns (bool)

465

{

466

return

467

interfaceId == type(IPlugin).interfaceId ||

468

super.supportsInterface(interfaceId);

469

}

470

}

471

```

472

473

### Advanced Interface Detection

474

475

```solidity

476

import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";

477

478

contract AdvancedInterfaceDetector {

479

using ERC165Checker for address;

480

481

struct InterfaceInfo {

482

bytes4 interfaceId;

483

string name;

484

bool required;

485

}

486

487

mapping(string => InterfaceInfo[]) public protocolInterfaces;

488

489

function defineProtocol(

490

string memory protocolName,

491

bytes4[] memory interfaceIds,

492

string[] memory interfaceNames,

493

bool[] memory required

494

) external {

495

require(

496

interfaceIds.length == interfaceNames.length &&

497

interfaceIds.length == required.length,

498

"Array length mismatch"

499

);

500

501

delete protocolInterfaces[protocolName];

502

503

for (uint i = 0; i < interfaceIds.length; i++) {

504

protocolInterfaces[protocolName].push(InterfaceInfo({

505

interfaceId: interfaceIds[i],

506

name: interfaceNames[i],

507

required: required[i]

508

}));

509

}

510

}

511

512

function checkProtocolCompliance(

513

address contractAddr,

514

string memory protocolName

515

) external view returns (bool isCompliant, string[] memory missing) {

516

InterfaceInfo[] memory interfaces = protocolInterfaces[protocolName];

517

string[] memory temp = new string[](interfaces.length);

518

uint missingCount = 0;

519

520

for (uint i = 0; i < interfaces.length; i++) {

521

bool supports = contractAddr.supportsInterface(interfaces[i].interfaceId);

522

523

if (!supports && interfaces[i].required) {

524

temp[missingCount] = interfaces[i].name;

525

missingCount++;

526

}

527

}

528

529

isCompliant = (missingCount == 0);

530

missing = new string[](missingCount);

531

532

for (uint i = 0; i < missingCount; i++) {

533

missing[i] = temp[i];

534

}

535

}

536

}

537

```