remix编译运行solidity9
昨天学了enum枚举是一个常量,是一个数据类型,常用于表示等级、任务的状态机
昨天还了解了loyalty是web3增长最快的商业模式。并且在linkedin上查看了一些web3的岗位并发到了公众号上。
前天学了合约的继承,多重继承,多重继承。
好的!现在正式进入 今天任务 3:阅读 ERC20 标准合约。
我们采用 OpenZeppelin 最经典、最清晰的 ERC20 实现(简化版),并且我会带你逐行分析,让你未来看任何 DeFi 协议都不会卡。
📘 标准 ERC20(教学用简化版)
下面这个版本去掉了复杂模块(如 ERC20Permit、ERC20Votes),保留完整核心逻辑,适合第一次阅读:
1 | |
核心函数
我来帮你逐个讲解你这个 ERC20 合约里的核心函数,以及如何在 Remix 的 Deployed Contracts 页面里测试它们。我们按功能来拆解,尽量结合操作示例,直观易懂。
1️⃣ balanceOf
1 | |
作用:
- 查询某个地址的 代币余额。
- 不修改区块链状态,只是读取。
使用方法:
- 部署合约后,在 Deployed Contracts 找到
balanceOf函数。 - 输入一个地址(比如部署者地址)。
- 点击
call→ 返回该地址的代币数量。
注意:
- 如果还没有 mint 过代币,余额是 0。
2️⃣ transfer
1 | |
作用:
- 将自己账户里的代币转给别人。
- 调用者是
msg.sender(也就是发送者),只会扣自己的余额。
使用方法:
在 Deployed Contracts 找到
transfer。输入:
to→ 对方地址amount→ 代币数量
点击
transact(这里会修改链上状态)。使用
balanceOf可以验证转账是否成功。
注意:
- 余额不足会
revert→ 报"balance too low"。 - 不能给 0 地址转账 → 报
"to zero"。
3️⃣ approve
1 | |
作用:
- 授权别人(spender)可以 从你的账户花费一定数量的代币。
- 并不会真正转币,只是设置 allowance。
使用方法:
找到
approve函数。输入:
spender→ 被授权者地址amount→ 允许花费的代币数量
点击
transact→ 会触发Approval事件。可以通过
allowance查看授权额度。
注意:
- 没有余额也可以 approve,只是别人 transferFrom 时会失败。
4️⃣ allowance
1 | |
作用:
- 查询
owner给spender授权了多少代币。
使用方法:
找到
allowance。输入:
owner→ 授权人地址spender→ 被授权人地址
点击
call→ 返回剩余可用额度。
5️⃣ transferFrom
1 | |
作用:
被授权者(msg.sender)从别人账户
from转代币到to。需要:
from已经 approve msg.sender 足够额度from有足够余额
使用方法:
- Alice approve Bob 50 个 token
- Bob 调用
transferFrom(Alice, Charlie, 20) _allowances[Alice][Bob]会减少 20_balances[Alice]扣 20,_balances[Charlie]加 20
注意:
- 余额或授权不足会 revert
- msg.sender = 调用这个函数的人
6️⃣ _mint
1 | |
作用:
- 铸造新的代币给某个地址
- 增加
totalSupply - 增加
to的余额
使用方法(Deployed Contracts 不能直接调):
_mint是 internal,只能在合约内部调用(比如部署时或写一个 public mint 函数)。- 如果想测试,需要在合约里加一个 public 包装函数:
1 | |
- 然后在 Deployed Contracts 调用
mint,自己给自己增加代币。
7️⃣ _burn
1 | |
作用:
- 销毁代币
- 扣除
from的余额 - 减少
totalSupply
使用方法(Deployed Contracts 不能直接调):
- 同
_mint,需要一个 public 包装函数:
1 | |
- 然后调用
burn,会减少你账户的代币和总量。
8️⃣ 在 Deployed Contracts 中测试流程示例
假设你想完整测试:
mint 给自己代币
- 如果部署时没有 initial supply,需要写 public mint
- mint(自己地址, 100)
查询余额
- balanceOf(自己地址) → 100
transfer 给别人
- transfer(朋友地址, 10)
- balanceOf(自己) → 90
- balanceOf(朋友) → 10
授权别人
- approve(朋友地址, 20)
别人调用 transferFrom
- balanceOf(朋友) = 0
- 朋友调用 transferFrom(自己, 朋友, 10)
- balanceOf(自己) → 80
- balanceOf(朋友) → 10
- allowance(自己, 朋友) → 10
burn 自己代币
- burn(5)
- balanceOf(自己) → 75
- totalSupply 减少 5
如果你愿意,我可以帮你画一张 ERC20 流程图,把 balanceOf、transfer、approve、transferFrom、mint、burn 一张图展示出来,流程直观、链上操作对应关系清晰。
你希望我画吗?
这个代码里好像没有代币,转账的时候老是失败
对,你观察得非常准确——你现在之所以 转账总是失败,是因为你的合约里 没有给任何地址分配代币。
让我们分析一下你这个 ERC20 合约的情况:
1️⃣ 为什么转账失败
在你的 _transfer 函数里有这个检查:
1 | |
balances是一个 mapping,默认所有地址的余额都是 0。- 你部署合约时没有调用
_mint,所以 任何地址的余额都是 0。 - 所以当你调用
transfer或transferFrom时,都会 revert 报"balance too low"。
2️⃣ 如何给自己代币
你的 _mint 是 internal,所以 Deployed Contracts 页面不能直接调用。你有两种办法:
方法 A:在合约里加一个 public mint 包装函数
1 | |
然后:
- 在 Deployed Contracts 找到
mint - 输入
to = 你的地址,amount = 100 - 点击
transact→ 你账户就有 100 个代币 - 再用
balanceOf查看余额,确认成功
方法 B:部署时直接初始化余额
1 | |
- 部署完成后,部署者账户自动有代币
- 然后直接可以
transfer给别人
3️⃣ 测试顺序示例
- 给自己 mint 100 个代币
balanceOf(自己)→ 100transfer(朋友, 10)→ 成功balanceOf(朋友)→ 10
💡 总结
- ERC20 不像 ETH,部署合约后 不会自动给你代币
- 必须通过
_mint给自己分配,或者在 constructor 里初始化 - 没有代币,任何转账都会
"balance too low"
如果你愿意,我可以帮你写一个 完整可在 Remix 测试的 ERC20 版本,自带 mint/transfer/approve/transferFrom 流程,部署就能直接测试转账。
你希望我直接帮你写吗?
deploy

输入名字alice,输入字符1234,点击transact。成功部署了ERC20合约
name\ symbol

成功获取到alice和1234
mint

在mint输入eth和100,点击transact
给这个eth地址转100个代币
输入balanceof,和eth地址,输入call
显示100
说明eth地址有100个代币
注意: 如果你的合约没有mint说明,你在sol代码中没有添加public
要在合约里加一个 public mint 包装函数
1 | |
📌 逐模块深度解析(DeFi 开发 & Auditing 必须掌握)
🔹 1. 状态变量(name/symbol/decimals/totalSupply)
ERC20 规定:
name— 代币名称(Human readable)symbol— 代币符号decimals=18— 大多数代币默认 18 位(仿照 ETH)totalSupply— 总发行量
注意:ERC20 标准本身没有规定 decimals 必须是 18,USDC 是 6。
🔹 2. 核心数据结构:balances & allowances
📍 balance(余额)
1 | |
记录每个地址的 Token 数量。
📍 allowance(授权)
1 | |
表示“owner 授权给 spender 可以花费 owner 的多少 Token”。
你未来看 AMM、DEX、路由器、桥,会发现 90% 操作都是:
1 | |
🔹 3. event(日志)
ERC20 标准规定了两个必须的 event:
Transfer
1 | |
用途:
- 前端(React, Dapp)监听代币转账
- 区块浏览器(Etherscan)展示转账记录
- The Graph 做索引
Approval
1 | |
应用场景:
- 前端钱包(Metamask)弹出“授权金额”窗口
- DEX 调用 transferFrom 前必须看到授权成功
🔹 4. transfer / transferFrom 是 ERC20 的核心
transfer
1 | |
直接从 msg.sender 转给 to。
这是最常见的路径。
transferFrom
1 | |
逻辑:
- spender(msg.sender)必须已经获得 owner 的授权
- allowance 要足够
- 花掉授权额度
- 最终调用
_transfer
所有 DEX(Uniswap)都用 transferFrom 实现 swap。
🔹 5. _transfer / _mint / _burn 是内部功能
真实项目永远不会让用户直接调用 _mint/_burn,你会在子合约里这样写:
1 | |
_transfer 是逻辑核心——你未来写的任何代币模型(税费、销毁、黑名单、额度控制),都需要重写 _transfer 来实现。
📘 你的第一个阅读任务:请你回答这 7 个问题
这是为了确保你真的理解了 ERC20 逻辑,而不是只读一遍。
请你回答:
① 为什么 transfer 不直接写逻辑,而是调用内部函数 _transfer?
因为任何代币模型都需要重写_transfer来实现。
我猜测调用内部函数_transfer来能节省gas费。
直接写逻辑,在遇到多次转账的时候就需要写多次的逻辑,不如调用内部函数_transfer方便
你的答案 非常接近标准答案,而且已经有明显的“工程思维 + 可扩展性意识”了。
下面我将用 专业 Solidity 工程师 / DeFi 开发者 / Auditor 的角度,帮你把这个问题补全到完全体。
✅ ① 为什么 transfer 不直接写逻辑,而是调用内部函数 _transfer?
你的三个理由都成立,但还没有触及“核心原因”。
我帮你拆成 5 个必须掌握的高级理由(你已经答对了其中 3 个)。
最终标准答案(专业级)
💠 1. 避免逻辑重复(你答对)
ERC20 核心转账逻辑有两处需要调用:
transfer()transferFrom()
如果不抽出 _transfer,你就要写两份完全一样的代码,看起来像这样:
1 | |
重复代码 = bug 概率翻倍
抽一个 _transfer:
一个地方改动,两处逻辑同步。
✔ 你已正确说到这一点。
💠 2. 子合约方便扩展(你答对)
比如你想做:
- 黑名单代币(BlackList)
- 征税代币(3% Tax)
- 铸造奖励代币(Mining Token)
- 反射代币(Reflection Token)
全部都要基于 _transfer 扩展。
例如:
1 | |
如果 transfer 和 transferFrom 都有独立逻辑,你就 无法统一重写。
✔ 你也答到了这一点。
💠 3. 节省 Gas(你猜得对)
理由非常具体:
_transfer在 bytecode 中只有一份transfer和transferFrom都 jump 到同一段逻辑- 如果重复写,会让 bytecode 增加几十字节,部署费增加
- 执行时也会多消耗 gas(因为指令更多)
所以:
共享内部函数 = 节省部署成本 + 节省调用成本
你直觉很准。
💠 4. 提高可审计性(真正的关键)
在 ERC20 这种规模化使用的合约里,“可审计性”是非常关键的考量。
抽象出 _transfer 后:
- 核心逻辑全部在一个函数里 → 审计只需重点看
_transfer - 任何外部操作最终都要走
_transfer - Auditor 只要找到一个函数就能检查全部风险点
如果逻辑散落在 2-3 个函数里,审计难度会上升很多。
优秀智能合约 = 可读性高 = 审计清晰 = 风险更低
💠 5. 允许多重继承组合扩展(高级)
例如:
- ERC20
- ERC20Burnable(可烧毁)
- ERC20Pausable(可暂停)
- ERC20Permit(EIP-2612)
- ERC20Votes(治理)
- ERC20FlashMint(闪电铸币)
它们全部依赖 _transfer。
OpenZeppelin 的组合方式是:
1 | |
全部 override _transfer 实现功能叠加。
如果每个地方都写自己的一套 transfer 逻辑,会直接爆炸。
🎯 总结(你可以记住这句话)
ERC20 抽出
_transfer的根本原因不是语法,而是工程设计:复用、可扩展、可审计、可继承、可组合、节省 Gas。
你给出的三点:
- 重写方便 ✔
- 不重复逻辑 ✔
- gas 可能更省 ✔
都是正确的基础理由,已经不错。
但以上五点才是完整的专业答案。
② transferFrom 为什么需要 allowance,而 transfer 不需要?
在deployed contracts里面,transferFrom有三个参数,from,to,amount
transfer只需要两个参数to,amount。
根据这一行
allowances[from][msg.sender] = currentAllowance - amount;
transferFrom需要allowance,因为需要发送者支付转账手续费,是从现有的allowance减去数量
transfer不需要,因为没有from参数,默认扣除了msg.sender的转账手续费。
你的答案方向基本正确,但还没有抓到 ERC20 设计的最核心动机。
我来带你把这题补全到 DeFi 开发者 / 审计师级别的完整理解。
✅ ② transferFrom 为什么需要 allowance,而 transfer 不需要?
你的理解里有三点是对的:
- transfer 的“from”永远是 msg.sender,所以默认自己给别人转 → 不需要授权 ✔
- transferFrom 的调用者(msg.sender)不是 from → 需要授权 ✔
- allowance 每次使用都会减少(扣减授权额度)✔
这些全部对,但下面是行业级的“核心理由”。
🔥 ERC20 标准设计 transferFrom 的真正原因:授权第三方代你花钱
🧠 transfer:自己花自己的钱
1 | |
相当于:
“我自己给别人转钱,不需要别人授权我。”
msg.sender 本来就是要花钱的人,所以不需要 allow。
🧠 transferFrom:让别人代你花钱(核心设计)
1 | |
这里出现了三个角色:
| 参数 | 意义 |
|---|---|
| from | 钱来自谁(代扣对象) |
| to | 钱转给谁 |
| msg.sender | 实际执行交易的人(第三方) |
举例:
用户 U = from
Uniswap 路由器 = msg.sender
当用户在 Uniswap Swap ETH → Token 时:
用户先授权:
1
approve(Uniswap, 100 USDC)Uniswap 自动调用:
1
transferFrom(U, Pool, 100 USDC)
这就是为什么它必须要 allowance!
transferFrom 是“受托花钱”机制。
需要用户提前授权,否则任何人都可以随便扣你的钱。
🔥 你提到的 “手续费” 部分 —— 有一点误解
你写:
transferFrom 需要 allowance,因为需要发送者支付手续费,是从现有的 allowance 减去数量
这里你把“手续费(gas)”和“代扣金额”混在了一起。
######### ❌ allowance 扣的不是手续费
######### ✔ 扣的是 “from 地址要被扣的 token 数量”
手续费(gas)永远由 msg.sender 支付,与 allowance 无关。
流程应该是:
- user(from)支付 Token
- caller(msg.sender)支付 gas
这是 ERC20 的核心安全模型。
🔥 最专业的解释(你可以记住这一句)
transferFrom 存在的原因,是让第三方(如 DEX、合约、机器人)代你执行转账。
为了防止第三方乱扣钱,必须 require 授权,即 allowance。
🎓 你已经理解了 90%,我把你的答案升级后的总结写给你:
你理解的是:
- transfer 默认用 msg.sender 当 from → 不需要授权
- transferFrom 有独立的 from → 必须授权
补充的专业版理解:
- transferFrom 是“第三方代扣模式”
- 为了安全必须用 allowance 明确用户授权额度
- DEX / lending / routers / NFT 市场全部依赖 transferFrom
- allowance 减少的是“可扣额度”,不是手续费
- 手续费永远由交易发起者(msg.sender)付
你现在已经完全理解了。
为什么不能用户自己 transfer,而是要让“第三方代扣 from 的钱”?
答案很简单:
因为用户不可能在每一次复杂 DeFi 操作中手动一步步转账。
transferFrom 让智能合约自动化处理整个流程。
🚀 1. 你在 DeFi 里使用一个功能 ≠ 单次转账,而是至少 3–10 次操作
如果没有 transferFrom,你做任何 DeFi 操作都会变成:
- 用户手动转账一次
- 用户再手动调用一个函数
- 再转账
- 再调用
- 再转账……
人类根本不会这样用。
举例你就懂了👇
📌 黄金案例:用户在 Uniswap 上买一枚代币
不使用 transferFrom 会发生什么?
👇
❌ 如果没有 transferFrom(很麻烦)
要买 100 USDC 价值的 ETH:
- 用户先
transfer(USDC → UniswapPool, 100) - 然后用户再手动调用
swap() - 再手动确认返回代币
- 最后 Uniswap 再手动把 ETH 转回给你
而且每一步都要 用户亲手执行 → 完全无法自动化。
这是 Web2 操作,不是 DeFi。
✔ 如果使用 transferFrom(智能合约自动处理)
用户只做一件事:
1 | |
授权一次之后,Uniswap 可以自动为你做:
- transferFrom(from=用户, to=Pool, amount) → 自动代扣
- 进行交换
- 把结果 token 自动发送给你
也就是:
一次点击,合约完成多步操作。
这就是 DeFi 的自动化本质。
📌 2. DeFi 操作几乎都需要“合约代扣”
下面每个真实场景都必须使用 transferFrom:
🏦 (场景 1)在借贷协议 Aave / Compound 中抵押资产
用户要抵押 1000 USDC。
如果没有 transferFrom:
- 用户手动 transfer USDC 给合约
- 用户再调用 deposit
- 合约还要检查余额是否正确
- 流程超级麻烦,还容易出错
✔ 有 transferFrom:
用户只做一步:
1 | |
然后 LendingPool 执行:
1 | |
完全自动抵押。
🧠 (场景 2)在 NFT 市场 OpenSea 购买 NFT
NFT 市场需要你付款,但市场本身不能动你的钱,除非你授权。
所以流程是:
1 | |
否则 NFT 市场无法收款。
🔁 (场景 3)做 DCA(定投)、自动换币、自动付费机器人
比如:
- 每天自动买 10 USDC 的 ETH
- 自动支付手续费
- 自动做市 AMM rebalance
这些都无法让用户每天手动执行:
1 | |
全部必须由机器人或合约完成 → transferFrom 必须存在。
💰 (场景 4)Token 的授权给 spender(比如质押池)
质押池必须从用户钱包扣 Token:
1 | |
否则用户无法质押(质押本质上就是把代币转入池中)。
📦 (场景 5)跨链桥(Bridge)锁定资产
桥需要锁你的 Token,然后在另一个链铸造。
流程:
1 | |
没有 transferFrom 就没法锁你的 Token。
🎮 (场景 6)链游 GameFi 自动扣用户的 Token 购买道具
你不会每买一次武器就手动转账。
所以:
1 | |
游戏自动扣费。
🕹 (场景 7)订阅付费 Web3 Subscription
比如:
- 每月自动扣 10 USDC
- 每天扣 gas
- 自动续费会员 NFT
- 自动支付预言机手续费(Chainlink)
全部必须 transferFrom,否则需要你手动转账 → 完全无法自动化。
📌 如果没有 transferFrom,就没有 DeFi。
DeFi 的核心是:
自动化金融
自动化意味着:
- 合约要有权限代替用户执行动作
- 用户只需要做授权
- 后续所有多步流程自动完成
没有 transferFrom,所有 DeFi 操作都要你自己执行 10+ 步。
这不叫 DeFi,这叫 Web2 手动流程。
🎯 核心总结(写得非常清晰)
transfer:自己给别人转钱,不需要授权
transferFrom:让合约或第三方自动代你扣钱,所以必须授权
transferFrom 是 ERC20 里 最重要 的函数,没有它:
- 没有 DEX
- 没有借贷
- 没有质押
- 没有 NFT 市场
- 没有链游
- 没有订阅
- 没有跨链桥
- 没有机器人
- 没有任意复杂交易组合
Web3 会直接倒退回单机时代。