new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Smart Contract Auditor · all subjects

Token quirks and integration hazards

5 notes, read out of this brain and free to use. Each one was extracted from a source and is re-checked against its exam.

Why does my code fail on USDT but work on every test token?

Because USDT (mainnet) violates the ERC-20 return-value convention: its `transfer`/`transferFrom`/`approve` return nothing instead of `bool`. A contract doing `require(token.transfer(...))` compiled against `IERC20` reverts on the missing return data. Fix: OpenZeppelin `SafeERC20` (`safeTransfer`, `safeTransferFrom`, `forceApprove`), which tolerates both conventions. Related USDT quirks worth checking in an audit: (1) `approve` reverts when changing a non-zero allowance to another non-zero value — use `forceApprove` (OZ 5.x) or approve(0) first; (2) USDT is upgradeable and has a fee-on-transfer switch (currently off — don't assume it stays off); (3) USDT/USDC are blacklistable — a protocol that can't handle a blacklisted user can brick withdrawals for everyone (consider pull patterns). Detection signals: raw `IERC20(token).transfer(...)` with require; `approve(spender, x)` called twice without zeroing. Rule: any integration targeting mainnet stables must be tested against forked mainnet USDT, not a mock.

Are ERC-777 and ERC-1363 tokens dangerous to integrate?

They add transfer-time hooks — `tokensReceived` (777) / `onTransferReceived` callbacks (1363) — which hand control flow to the recipient mid-transfer. That's a reentrancy vector inside what looks like a simple `transfer`: Cream Finance's 2021 exploit rode exactly this. Even without 777, remember ERC-20 `transfer` to a contract is fine (no hook), but `safeTransferFrom` in ERC-721/1155 DOES callback (`onERC721Received`) — NFT vault accounting updated after the safeTransfer is reentrant. Detection signals: state changes after ANY token transfer call; integrations that assume "it's just an ERC-20" without checking the token's actual code; `ERC777`/`ERC1820` registry lookups in dependencies. Fixes: treat every token movement as an external call (CEI + reentrancy guard), or restrict to an allowlist of known token implementations. For reports: hook-enabled tokens are a medium unless you show the concrete reentrancy path to value loss — then it's high. Always build the PoC.

How do unbounded loops over dynamic arrays become DoS bugs?

Two ways. (1) Gas-griefing growth: a function iterates `for (uint i; i < users.length; i++)` to distribute rewards or compute totals; anyone can register (permissionless `join()`), so the array grows until the loop exceeds block gas limit and the function is permanently uncallable — funds locked. This is a real incident class (early yield aggregators, GovernMental-style payout contracts). (2) External calls in a loop: `for (...) { users[i].call{value:...}("") }` — one reverting recipient DoSes everyone (that's why pull-over-push exists). Detection signals: loops over arrays that grow via permissionless calls; `push` without an upper bound; loops containing external calls or token transfers; `delete array` on huge arrays (still O(n) on some paths). Fixes: pagination (`claimable(start, end)`), pull-over-push claims, Merkle-distributor airdrops (O(1) claim with proof), enumerable-set caps. In reports, prove it: a foundry test that fills the array until `distribute()` runs out of gas turns a "theoretical" medium into a solid high.

What should I check about selfdestruct and forced ETH?

`selfdestruct` has two audit-relevant faces. (1) Presence in any library/implementation contract = finding (Parity freeze); post-Cancun (EIP-6780), `selfdestruct` only actually deletes the contract if called in the same transaction that created it, but it STILL force-sends its ETH balance — so the semantic you must audit is the forced-send. (2) Forced ETH as an attack: any contract can receive ETH via `selfdestruct` from a sacrificial contract or via coinbase payments, so accounting like `require(address(this).balance == totalDeposits)` or reward math keyed off raw `balance` can be broken by a dust donation — same class as the vault donation attack. Detection signals: `address(this).balance` used in equality checks or share pricing (use an internal accounting variable instead); `selfdestruct`/`suicide` opcode anywhere; missing `receive()` with logic assuming ETH can't arrive. Note as of early 2026: post-EIP-6780, "selfdestruct doesn't work anymore" is a common FALSE claim — the forced-ETH vector survives.

Are unchecked low-level call returns worth reporting?

Yes, when value or logic depends on the call succeeding. `addr.call{value: x}("")` returns `(bool ok, bytes)` — if the return is ignored and the call fails (out of gas, reverting receiver), the contract continues as if the ETH was sent: accounting says paid, receiver got nothing. Same for `token.call(abi.encodeWithSignature("transfer(...)"))` without checking return data. Detection signal (grep): `\.call\{` and `.call(` lines whose results aren't captured or required; also `abi.encodeWithSignature` with hand-typed signatures (typo in the string = silent failure, no compiler check — prefer `abi.encodeCall`). Severity calibration: ignored return on a user-initiated refund = medium/high (user loses funds); ignored return on a best-effort call with no accounting = low/info. Related: `try/catch` that catches but swallows all failures including your own bugs — catch specific revert reasons or log and re-throw. Solidity's high-level ERC-20 calls revert properly; the danger zone is assembly and low-level calls.

Give your agent this brain