Production and regulated tokens often need controls beyond mint and burn: pausing transfers during an incident, blocklisting sanctioned or hacked addresses, capping the maximum supply, and assigning permissions to multiple parties. OpenZeppelin provides Pausable, AccessControl (named roles), and ERC20Capped as composable building blocks.
1contract Token is ERC20, AccessControl, Pausable, ERC20Capped {2bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");3bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");45function mint(address to, uint256 amt) external onlyRole(MINTER_ROLE) {6_mint(to, amt); // ERC20Capped reverts if this exceeds cap()7}8function pause() external onlyRole(PAUSER_ROLE) { _pause(); }910// Transfers are blocked while paused (enforced in the _update hook)11function _update(address from, address to, uint256 v)12internal override whenNotPaused { super._update(from, to, v); }13}
Each control is also a centralization power. A pause can halt an exploit — or freeze legitimate users. Blocklists (used by USDC and USDT) let the issuer freeze stolen funds, but also mean the issuer can freeze YOU. ERC20Capped enforces a hard maximum supply. Role-based access lets you separate duties — a minter that cannot pause, a pauser that cannot mint — instead of one all-powerful owner.
Audit the powers: read who holds DEFAULT_ADMIN_ROLE (it can grant every other role) and the mint/pause roles. Those keys should live behind a timelock or multisig, not a single externally-owned account. 'Who can freeze or inflate this token, and how are those keys secured?' is one of the most important questions about any token.
1// Capped supply enforcement (simplified):2function _update(address from, address to, uint256 value) internal override {3super._update(from, to, value);4if (from == address(0)) { // a mint5require(totalSupply() <= cap(), "cap exceeded");6}7}
Connect your wallet to mark this lesson as complete
Earn 25 EFFORT tokens