Skip to main content

deepidea

 · 

Reading the USDC Contract: The 31 Write Functions and 17 Events You Overlooked

A teardown of USDC's main contract, FiatTokenV2_2: 24 view / 31 write / 17 events. How Circle splits power across 5 roles, how V2.2 packs balance and blacklist into a single storage slot, why rescuer is currently 0x0, and why both EIP-2612 and EIP-3009 coexist.

Contract: 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 (proxy) → impl 0x435068…02dd
Version: FiatTokenV2_2 (Solidity 0.6.12, Apache-2.0, Circle)
Size: 24 view / 31 write / 17 events — 3× larger than a typical ERC-20. We’ll unpack why.


Read Functions (24)

1️⃣ ERC-20 Standard (6)

name() · symbol() · decimals() · totalSupply()
balanceOf(address) · allowance(owner, spender)

2️⃣ EIP-712 Domain Signature Constants (5 pure-view hashes)

DOMAIN_SEPARATOR()
PERMIT_TYPEHASH()                     ← EIP-2612
TRANSFER_WITH_AUTHORIZATION_TYPEHASH ← EIP-3009
RECEIVE_WITH_AUTHORIZATION_TYPEHASH  ← EIP-3009
CANCEL_AUTHORIZATION_TYPEHASH        ← EIP-3009

👉 Insight: USDC implements both EIP-2612 (permit) and EIP-3009 (transferWithAuthorization). The former is a signed allowance; the latter is a signed direct transfer — overlapping functionality, different semantics. EIP-3009 predates 2612, and Circle kept it to stay forward-compatible with their own V2 contract shipped in 2020.

3️⃣ Nonce State (2)

nonces(address owner)                       ← EIP-2612, monotonic counter
authorizationState(authorizer, bytes32 nonce) ← EIP-3009, bool flag

👉 Insight: Two nonce strategies coexist. permit uses monotonic increment (storage-efficient); EIP-3009 uses a nonce set (allows out-of-order / concurrent authorizations — better suited to bots and payment flows).

4️⃣ Permission / Role Queries (7)

owner() · masterMinter() · pauser() · blacklister() · rescuer()
isMinter(address) · minterAllowance(address)

👉 Insight: 5 separated roles + delegated minting is the core architectural difference between USDC and USDT. Tether has a single owner; USDC carves authority into 5 slots.

5️⃣ Risk Query (1)

isBlacklisted(address)

6️⃣ Other Metadata (3)

version() · currency() · paused()

Write Functions (31 — including 5 overloads)

1️⃣ ERC-20 Standard (5, all whenNotPaused + notBlacklisted)

transfer · transferFrom · approve
increaseAllowance · decreaseAllowance

👉 Insight: approve also carries whenNotPaused — once Circle pauses the contract, you can’t even change your allowance. That’s stricter than “just freeze transfers.”

2️⃣ Gasless Signed Transfers (8 = 4 functions × 2 overloads)

permit(...)                       × 2  ← (v,r,s) and (bytes signature)
transferWithAuthorization(...)    × 2
receiveWithAuthorization(...)     × 2  ← extra msg.sender check vs transfer, anti front-run
cancelAuthorization(...)          × 2

👉 Insight: Each function has two signature overloads — classic (v, r, s) and packed bytes signature. The packed variant exists to support EIP-1271 smart-contract wallets (Safe, AA wallets). This is one of the core motivations behind the V2.2 upgrade.

3️⃣ Mint / Burn System (4)

mint(address to, uint256 amount)
burn(uint256 amount)
configureMinter(minter, allowance)
removeMinter(minter)

👉 Insight: Minting is distributed and capped. masterMinter assigns each minter a cap that’s decremented per-mint — so even if a minter key leaks, the blast radius is bounded by that minter’s allowance. Tether has no minter allowance concept; a single key controls all supply.

4️⃣ Admin (5, owner-only)

transferOwnership · updateMasterMinter · updatePauser
updateBlacklister · updateRescuer

5️⃣ Risk Controls (4)

pause() · unpause()           ← pauser only
blacklist · unBlacklist       ← blacklister only

6️⃣ Asset Rescue (1)

rescueERC20(tokenContract, to, amount)   ← rescuer only

👉 Insight: This function is currently dead. Live state shows rescuer() = 0x0000…0000, meaning Circle has intentionally set the rescuer to the zero address — any other ERC-20 accidentally sent to the USDC contract itself is now permanently stuck.

7️⃣ Upgrade Initializers (4, one-shot each)

initialize · initializeV2 · initializeV2_1 · initializeV2_2

👉 Insight: The 4 initializers expose the full upgrade history: V1 → V2 (adds EIP-2612 / EIP-3009) → V2.1 (adds lostAndFound capture) → V2.2 (blacklist storage refactor).


Events (17)

Group Events
ERC-20 Transfer, Approval
EIP-3009 AuthorizationUsed, AuthorizationCanceled
Mint/Burn Mint, Burn, MinterConfigured, MinterRemoved
Risk Pause, Unpause, Blacklisted, UnBlacklisted
Role changes OwnershipTransferred, MasterMinterChanged, PauserChanged, BlacklisterChanged, RescuerChanged

👉 Insight — 17 events = 17 monitorable on-chain signals. USDT exposes only ~10 events; it lacks destroy / minter allowance / Authorization-class events.


5 High-Value Takeaways

1. USDC is an “institutional compliance template.”
5 separated roles (owner / masterMinter / pauser / blacklister / rescuer), each capability minimized — no single key compromise can sink the system. Tether runs a single-key, single-owner architecture.

2. V2.2’s storage trick is an underrated gas-saver.
balanceAndBlacklistStates packs balanceOf(address) and isBlacklisted(address) into the same storage slot (high bit flags blacklist, low 255 bits store the balance). Result: a blacklist check goes from an extra cold SLOAD (2100 gas) to zero.

3. EIP-3009 fits bots and payments better than EIP-2612.
The nonce is a bytes32 set, not a monotonic counter — multiple concurrent authorizations can settle out-of-order. That’s why Circle’s own CCTP / Circle Pay / Stripe USDC payouts all use transferWithAuthorization.

4. EIP-1271 overloads exist for AA wallets.
All 4 signature functions are overloaded — (v, r, s) and bytes signature. The latter enables EIP-1271 contract signatures, which means a Safe multisig can gaslessly approve USDC via permit.

5. rescuer = 0x0 means Circle deliberately gave up the rescue power.
Other ERC-20s mistakenly sent to the USDC contract itself are permanently stuck. One fewer privileged account = one fewer attack surface.


All structural data and live values in this post were pulled and verified via the smarts.md MCP server.

Link smarts.md

Download Pickful App

Better experience on mobile

iOS

Android

APK