# 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*