📖 模块 04 — Solidity 基础语法 + ERC-20 合约部署(Day 4-5)
学习时长:2 天 | 产出:Sepolia 上的合约地址(
0x...)
对应路线:Day 4-5,学 Solidity 基础 + Remix 写 ERC-20,部署到 Sepolia存量代码 DeepSeek - Into the Unknown
Layout of a Solidity Source File — Solidity 0.8.35-develop documentation
Remix IDE - Smart Contract Development
智能合约简介 — Solidity 0.8.35-开发文档
合约永久存储与修改
🎯 任务目标
- [ ] 理解 Solidity 基本语法:数据类型、函数修饰符、事件、mapping
- [ ] 理解 ERC-20 标准:接口定义、6 个必须实现的函数
- [ ] 在 Remix IDE 中编写并编译 ERC-20 合约
- [ ] 连接 MetaMask(Sepolia)部署合约,获得合约地址
- [ ] 在 Etherscan 验证合约(Verify & Publish)
- [ ] 调用合约函数:transfer、approve、allowance
📚 学习材料(序号编码)
M-01 — Solidity 官方文档(中文)
链接:https://docs.soliditylang.org/zh/latest/
重点章节(按顺序阅读):
| 序号 | 章节 | 重点内容 |
|---|---|---|
| M-01-A | /introduction-to-smart-contracts | 合约基本结构、Storage vs Memory |
| M-01-B | /types.html | uint、address、bool、string、bytes、mapping |
| M-01-C | /contracts.html | 函数、修饰符(modifier)、事件(event)、constructor |
| M-01-D | /units-and-global-variables.html | msg.sender、msg.value、block.timestamp |
预计时间:Day 4 上午,2-3 小时
M-02 — Remix IDE(在线合约编辑器)
链接:https://remix.ethereum.org
说明:无需安装,浏览器直接使用。提供编辑器 + 编译器 + 部署界面。
操作:打开后在左侧 File Explorer → 新建 .sol 文件 → 开始写代码
M-03 — ERC-20 标准文档
链接:https://eips.ethereum.org/EIPS/eip-20
说明:ERC-20 是以太坊最重要的代币标准,定义了 6 个函数 + 2 个事件。
重点阅读:Specification 部分(函数签名列表),5 分钟可读完。
M-04 — OpenZeppelin ERC-20 实现(参考)
链接:https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
说明:业界标准实现,读懂后面试能说"生产环境用 OpenZeppelin,不自己实现"。
建议:先自己手写一遍,再对照 OZ 实现理解差距。
M-05 — Sepolia Etherscan 合约验证教程
链接:https://docs.etherscan.io/tutorials/verifying-contracts-programmatically
简版操作(不需要命令行):
- 部署后复制合约地址
- 打开 https://sepolia.etherscan.io/verifyContract
- 粘贴合约地址,选择编译器版本,粘贴源码,提交
M-06 — 视频:Solidity 速成(可选)
链接:https://www.youtube.com/watch?v=ipwxYa-F1uY(Patrick Collins,32小时版可只看前2小时)
说明:如果文档阅读感觉枯燥,用这个视频引导。
推荐段落:0:00 - 2:00:00(变量、函数、合约部署)
🧠 概念精讲
0. Solidity 快速开始简单应用
1 | // SPDX-License-Identifier: MIT |

部署

选择账户, 方法进行调用

调用成功

查询count方法,交易次数出来

1. Solidity 核心语法速览
1 | // SPDX-License-Identifier: MIT |
2. 关键概念对比(金融科技视角)
| Web3 概念 | 金融科技类比 | 关键差异 |
|---|---|---|
mapping(address => uint256) |
数据库表(地址→余额) | 在链上,所有人可读,写入费 Gas |
event Transfer(...) |
数据库 Change Log / Kafka 消息 | 存在区块日志,不可修改,比 SSTORE 便宜 |
msg.sender |
请求中的 JWT Token 里的 userId | 由交易签名密码学保证,无法伪造 |
require(condition, msg) |
业务校验 + 抛异常 | 失败则 revert,Gas 退还(已用的不退) |
constructor |
Spring Bean 初始化 / 数据库初始脚本 | 只执行一次,部署时运行 |
view 函数 |
GET 接口(只读) | 本地节点执行,完全免费,不上链 |
非 view 函数 |
POST 接口(写操作) | 需要发交易,消耗 Gas,上链 |
3. ERC-20 标准接口
ERC-20 是一套约定,任何实现以下接口的合约都是"合法"ERC-20 代币:
1 | interface IERC20 { |
approve + transferFrom 的用途(类比:银行代扣授权):
approve(DeFi协议地址, 1000)= 授权某协议最多扣 1000 个代币transferFrom(你的地址, 协议地址, 500)= 协议用你的授权扣 500 个代币- 这是 Uniswap、Compound 等 DeFi 协议运作的基础
4. Storage vs Memory vs Stack
| 存储位置 | 生命周期 | 费用 | 用途 |
|---|---|---|---|
| Storage | 永久(区块链上) | 最贵(SSTORE 20000 Gas) | 状态变量(余额、名称等) |
| Memory | 函数调用期间 | 较便宜 | 函数内临时变量、字符串参数 |
| Stack | 极短(当前操作) | 几乎免费 | EVM 内部计算 |
| Calldata | 只读,函数调用期间 | 比 Memory 便宜 | external 函数的输入参数 |
🔧 动手实操步骤
Step 1:打开 Remix 并创建文件
- 打开 https://remix.ethereum.org
- 左侧 File Explorer → 点击「+」新建文件
- 文件名:
MyERC20.sol
Step 2:编写 ERC-20 合约
将以下完整合约复制到 MyERC20.sol:
1 | // SPDX-License-Identifier: MIT |

Step 3:编译合约
- 左侧点击「Solidity Compiler」图标(第2个)
- 编译器版本选择
0.8.20 - 点击「Compile MyERC20.sol」
- 左侧出现绿色勾 = 编译成功
常见错误排查:
pragma solidity ^0.8.20与编译器版本不匹配 → 调整编译器版本- 缺少分号 → 检查每行结尾


测试授权10

选择account2转账


转账成功

account1账户减少了5

account2账户增加5

Step 4:连接 MetaMask 并部署到 Sepolia
- 左侧点击「Deploy & Run Transactions」图标(第3个)
- Environment 选择
Injected Provider - MetaMask

- MetaMask 弹窗 → 选择 Sepolia 网络 → 连接
选择之前创建的maskwallet


一定要在设置->高级设置里面开启测试网络

- 确认 Account 显示你的 Sepolia 地址

- Contract 选择
MyERC20Token
- 展开 Deploy 下方的参数输入框:
1
2
3_name: "ZMERC20"
_symbol: "^"
_initialSupply: 1000 - 点击「Deploy」→ MetaMask 确认 → 等待交易上链
- 左下角「Deployed Contracts」出现合约地址 🎉

这里因为采用的测试忘了gas不够演示到这里就足够
📸 截图要求:Remix 底部显示合约地址的界面
Step 5:与合约交互
在 Remix 的 Deployed Contracts 区域,展开你的合约,可以:
读取数据(免费):
- 点击
name→ 显示 “TestToken” - 点击
totalSupply→ 显示 1000000000000000000000000(加了 18 位 0) balanceOf输入你的地址 → 显示全部余额
写入操作(消耗 Gas):
transfer:输入某个地址 + 金额,点击执行,MetaMask 确认- 执行后在 Etherscan 查看 Transfer 事件日志
Step 6:在 Etherscan 验证合约(可选但推荐)
- 复制部署后的合约地址
- 打开 https://sepolia.etherscan.io,搜索该地址
- 点击「Contract」→「Verify and Publish」
- 选择
Solidity (Single file),编译器版本v0.8.20,MIT 协议 - 粘贴源码,提交
- 验证成功后合约显示绿色勾,任何人可以读取源码
📸 截图要求:Etherscan 上合约页面(显示合约地址 + Contract 标签)
✅ 产出检查清单
- [ ] 在 Remix 中成功编译合约(无错误)
- [ ] 部署到 Sepolia,获得合约地址(记录:
0x_______________) - [ ] 在 Etherscan 上能查看到该合约
- [ ] 成功调用
transfer转账,在 Etherscan 看到 Transfer 事件日志 - [ ] 截图存档:① Remix 编译成功界面 ② 部署成功界面(显示合约地址)③ Etherscan 合约页面
📝 测试题
选择题
T01. 在 Solidity 合约中,mapping(address => uint256) public balanceOf 存储在哪里?调用 balanceOf(myAddr) 需要花费 Gas 吗?
A. Memory,每次函数调用结束后清空,调用免费
B. Storage(区块链上),读取需要发交易,消耗 Gas
C. Storage(区块链上),public 自动生成 view getter,链下调用免费
D. Calldata,只在交易执行期间存在,调用免费
参考答案
C。public 状态变量自动生成对应的 view 函数(getter)。view 函数在链下(通过 eth_call RPC)执行时完全免费,节点本地计算即可,不需要广播交易。但如果是在合约内部写 mapping 值(SSTORE),则消耗大量 Gas。
T02. ERC-20 合约的 emit Transfer(address(0), msg.sender, totalSupply) 在 constructor 中的含义是?
A. 把所有代币从零地址销毁
B. 表示代币从"无"(零地址代表铸造源头)转到部署者,是代币"铸造"的约定表示
C. 这是错误的,不应该 emit Transfer 到零地址
D. 向零地址发送代币,实际上没有任何效果
参考答案
B。ERC-20 标准中约定:Transfer 事件 from = address(0) 表示铸造(mint),to = address(0) 表示销毁(burn)。这是链上事件的语义约定,Etherscan 等工具按此规则显示"铸造"而非普通转账。
T03. 以下哪个 Solidity 函数调用不需要支付 Gas(在链下调用时)?
1 | function A() public { balanceOf[msg.sender] += 1; } |
A. 只有 A
B. B 和 C
C. 只有 C
D. A、B、C、D 都需要 Gas
参考答案
B。view(读取状态,不修改)和 pure(不读不写状态)函数在链下执行时免费。A 修改了状态变量,必须发交易消耗 Gas。D 是 payable,也需要发交易。
T04. approve(spenderAddr, 1000) 之后,spenderAddr 可以调用 transferFrom 转多少次?
A. 只能转一次,每次授权只能用一次
B. 可以转多次,只要累计金额不超过 1000
C. 可以无限转账,1000 只是最低限额
D. 取决于合约实现,ERC-20 标准未规定
参考答案
B。allowance 是累计额度,每次 transferFrom 会减少 allowance 余额。直到 allowance 归零前,spender 可以多次调用 transferFrom,但累计转出金额不能超过授权额度。
判断题
T05. Solidity 中 uint256 不会溢出,因为 pragma solidity ^0.8.0 以上版本默认启用溢出保护。(对 / 错)
参考答案
对。Solidity 0.8.0+ 默认对整数加减乘法启用溢出/下溢检查,溢出时自动 revert。如果确实需要不检查的算术(通常在确定不会溢出的场景,节省 Gas),可以使用 unchecked { ... } 块。
T06. 部署 ERC-20 合约到 Sepolia 后,合约代码可以被修改,只需要重新调用 Remix 的 Deploy 按钮即可更新。(对 / 错)
参考答案
错。区块链上的合约代码一旦部署不可更改(immutable)。重新 Deploy 会生成一个全新的合约地址,旧合约依然存在于链上。如果需要可升级合约,必须使用代理模式(Proxy Pattern),这是进阶话题。
简答题
T07. 解释 ERC-20 中 approve + transferFrom 机制的作用场景。
类比你金融科技背景,这与哪种业务模式类似?存在什么安全风险?
参考答案
作用:允许第三方合约(如 Uniswap)在得到用户授权的前提下,代为转移用户的代币。解决了"用户不需要先把代币转给协议,协议直接从用户账户扣款"的需求。
金融类比:银行代扣授权。用户签署代扣协议(approve),授权某平台(spender)每月从账户自动扣款(transferFrom),金额不超过授权上限。
安全风险:
- 无限授权:很多 DApp 让用户 approve 极大额度(uint256 最大值),若合约有漏洞或恶意,可无限转走用户代币
- 双重花费攻击(ERC-20 approve 竞争条件):修改 allowance 时,若先 approve(100) 再 approve(50),spender 可能在两笔 approve 之间抢先转走 100,再转走 50,共 150
- 解决方案:先 approve(0) 再 approve 新值,或使用 ERC-2612(Permit 签名授权)
T08. 你的 ERC-20 合约中,如果有人调用 transfer(address(0), 100)(转给零地址)会发生什么?
在 USDT(Tether)等真实合约中,这被允许吗?为什么要禁止?
参考答案
在你的合约中:执行了 require(to != address(0), "Transfer to zero address"),会 revert,交易失败,代币不会丢失。
若没有这个保护:转账会"成功",100 个代币会从你的余额扣除,但零地址没有私钥,无人能控制,代币永久丢失(相当于销毁)。
为什么禁止:防止意外"烧币"(burn)。如果需要销毁代币,应该通过专门的 burn() 函数(更明确的意图),而不是意外转给零地址。
真实 USDT:Tether 合约同样有对零地址的检查,防止意外丢失。
🔗 下一模块
完成本模块后 → 模块 04:Web3j 连接节点 → 读余额 → 监听事件
下一步用 Java(Web3j)与你刚部署的合约交互,跑通后端与区块链对接的 demo。