# ERC20 Deep Dive Analysis (2/n) - State Transition Flow, Security Analysis ## State Transition Flow ### Tranfer Flow - Scenario: Alice wants to transfer 50 USDC to Bob - Function flow: Alice calls `transfer(bob, 50)` - Check requirement - bob != address(0) - balances[alice] >= 50 - If fail, revert the tx - Update state - balances[alice] -= 50 - balances[bob] += 50 - Emit event - Transfer(alice, bob, 50) ### Approve & TransferFrom Flow - Scenario: Alice wants to approve Charlie to transfer 50 usdc for her to Bob - Function flow: - Approve: Alice calls `approve(charlie, 50)` - Update state: allowances[alice][charlie] += 50 - Emit event: Approval(alice, charlie, 50) - TransferFrom: Charlie calls `transferFrom(alice, bob, 50)` - Check requirement - balances[alice] >= 50 - allowances[alice][charlie] >= 50 - Update state - allowance[alice][charlie] -= 50 - balances[alice] -= 50 - balances[bob] += 50 - Emit event - Transfer(alice, bob, 50) ### Approve & TransferFrom Flow - DEX - Scenario: Alice swaps 50 USDC for ETH on Uniswap - Why approve + transferFrom - Alice can't directly send USDC to Uniswap and trust it will send ETH back - Uniswap needs permission to pull tokens atomically during the swap - If anything fails, entire transaction reverts (atomic swap) - Function flow - Step 1: Approve: Alice calls `usdc.approve(uniswapRouter, 50)` - Update state: allowances[alice][uniswapRouter] = 50 - Emit event: Approval(alice, uniswapRouter, 50) - Step 2: Swap: Alice calls `uniswapRouter.swapExactTokensForETH(50, ...)` - Uniswap internally calls `usdc.transferFrom(alice, uniswapPool, 50)` - Check requirement - balances[alice] >= 50 - allowances[alice][uniswapRouter] >= 50 - Update state - allowances[alice][uniswapRouter] -= 50 - balances[alice] -= 50 - balances[uniswapPool] += 50 - Emit event - Transfer(alice, uniswapPool, 50) - Uniswap sends ETH to Alice ### Mint Flow - Scenario: Token contract wants to create 100 new USDC for Alice - Function flow: Contract calls `_mint(alice, 100)` - Check requirement - alice != address(0) - totalSupply + 100 won't overflow uint256.max - Update state - totalSupply += 100 - balances[alice] += 100 - Emit event - Transfer(address(0), alice, 100) ### Burn Flow - Scenario: Alice wants to destroy 50 USDC from her balance - Function flow: Contract calls `_burn(alice, 50)` - Check requirement - alice != address(0) - balances[alice] >= 50 - Update state - balances[alice] -= 50 - totalSupply -= 50 - Emit event - Transfer(alice, address(0), 50) ## Security Analysis ### 1. Approve race condition (front-running attack) - Vulnerability ```solidity // Alice approves Bob for 100 tokens alice.approve(bob, 100); // Later, Alice wants to lower the allowance to 30 tokens // But Bob sees this tx in the mempool, can quickly call transferFrom before Alice's update bob.transferFrom(alice, bob, 100) // Then after Alice sets the new allowance for bob alice.approve(bob, 30) // Bob still can call transferFrom to transfer the 30 from Alice bob.transferFrom(alice, bob, 30) // Result: Bob transfers 130 tokens from Alice ``` - Mitigation ```solidity // Option 1: Set to 0 first, verify no unexpected spending, then set to the new value approve(spender, 0); approve(spender, newAmount); // Option 2: Use increaseAllowance/decreaseAllowance safeIncreaseAllowance(token, spender, additionalAmount); safeDecreaseAllowance(token, spender, subtractedAmount); ``` ### 2. Integer over/underflow - Solidity 0.8.0+ - Auto checks built-in - Revert on overflow/ underflow: eg. _totalSupply overflow check when minting new tokens - Can use `unchedked` to opt-out ### 3. totalSupply invariant - Must always hold ```solidity assert(sum_of_all_balances == _totalSupply); ``` - Maintained by - mint: increase both balance and totalSupply - burn: derease both balance and totalSupply - transfer: move between balances ## Reference - ERC20 approve issue in simple words: https://blog.smartdec.net/erc20-approve-issue-in-simple-words-a41aaf47bca6 - OpenZeppelin discussion to remove `increaseAllowance` `decreaseAllowance` from ERC20, and move it to extension contract: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/4583 - OpenZeppelin SafeERC20: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/1cf13771092c83a060eaef0f8809493fb4c04eb1/contracts/token/ERC20/utils/SafeERC20.sol#L69 ## Previous Blog - [ERC20 Deep Dive Analysis (1/n) - Core State Variables, Function Breakdown](https://hackmd.io/@ChloeIsCoding/BJaUUQ2axe) ## Discussion Found an error? Have questions? - Twitter: [@chloe_zhuX](https://x.com/Chloe_zhuX) - Telegram: [@Chloe_zhu](https://t.me/chloe_zhu) - GitHub: [@Chloezhu010](https://github.com/Chloezhu010) --- *Last updated: Oct 15th, 2025* *Part of my #LearnInPublic Solidity series*