本文将带你用 Foundry 亲手实现一枚基于 EIP-20标准 的 ERC20 代币,从环境初始化到脚本部署、AI辅助测试,拆解每一步底层机制。适合希望提升 高级Solidity 实战水平、将来深耕 稳定币 或 DeFi协议 开发与审计的读者。
关键词:ERC20代币、Foundry、EIP-20标准、Solidity、稳定币、DeFi协议、智能合约测试、Foundry脚本部署
快速课程路线总览
- 预备环境:Foundry CLI 安装与项目初始化
- 手动编写
ERC20:逐个实现name、decimals、totalSupply、balanceOf - 引入 OpenZeppelin:安全标准与降低代码复杂度
- 一键部署到本地/测试链:
.s.sol脚本与Makefile同步操作 - AI加速单元测试:兼谈Copilot/ChatGPT的写作与校验之道
第一步:从0到1部署Foundry开发环境
在开始写任何Solidity之前,一条指令即可拥有完整工具链:
# 安装
curl -L https://foundry.paradigm.xyz | bash
source ~/.bashrc # 或对应的 shell
foundryup # 更新
# 新建项目
forge init my-erc20
cd my-erc20项目目录结构应保持简洁:src/,test/,script/一目了然。
第二步:手工锻造ERC20合约核心方法
为什么选择手撸而非直接继承OpenZeppelin?
答案在于 透彻理解 EIP-20 接口,未来在自定义稳定币或治理代币时能避免“蒙眼复制”的安全风险。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IERC20 {
function name() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
// ...其余接口省略
}
contract MyToken is IERC20 {
string public constant name = "MyToken";
string public constant symbol = "MTK";
uint8 public constant decimals = 18;
uint256 public immutable totalSupply;
mapping(address => uint256) public balanceOf;
constructor(uint256 initialSupply) {
totalSupply = initialSupply * 10 ** decimals;
balanceOf[msg.sender] = totalSupply;
}
function transfer(address to, uint256 value) external override returns (bool) {
require(balanceOf[msg.sender] >= value, "Insufficient balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
return true;
}
}要点提炼
uint256 totalSupply做为 不可变量,节省 ~2 100 gas。- 切勿使用浮点数:小数处理由
decimals明文存放。 - 事件
Transfer/Approval建议立即补齐,否则中心化交易所将误判余额。
第三步:接入OpenZeppelin,两把锁更安全
手撸结束后,将 主分支 切换到使用 OpenZeppelin 的 “快车道”:
forge install OpenZeppelin/openzeppelin-contracts --no-commit随后在 foundry.toml 添加:
[profile.default]
remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]重构精简版合约:
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyTokenOZ is ERC20 {
constructor(uint256 init) ERC20("MyToken", "MTK") {
_mint(msg.sender, init * 10 ** decimals());
}
}进阶技巧
- 当需要 稳定币 mint/burn 机制 时,可用
ERC20Burnable搭配自定义角色控制。 - 未来的 DeFi协议 如闪电贷、dEX做市,通常要求代币增加 ERC20Permit,省去 approve 步骤。
第四步:Foundry脚本部署三步曲
脚本命名惯例 DeployMyToken.s.sol 放在 script/,最短可用三行完成:
pragma solidity ^0.8.20;
import "../lib/forge-std/src/Script.sol";
import "../src/MyTokenOZ.sol";
contract DeployScript is Script {
uint256 constant INIT_SUPPLY = 1_000_000;
function run() external {
vm.startBroadcast(); // 私钥从环境变量读取
new MyTokenOZ(INIT_SUPPLY);
vm.stopBroadcast();
}
}本地Anvil演示:
make deploy-anvil # Makefile封装: `forge script script/DeployMyToken.s.sol:DeployScript --rpc-url http://localhost:8545 --private-key $ANVIL_PK`第五步:用AI加速编写单测,不盲信
test/ 文件夹新建 MyTokenOZ.t.sol,先用 Foundry 原生能力写下场景:
function testTransfer() public {
uint256 amt = 100e18;
vm.prank(owner);
token.transfer(alice, amt);
assertEq(token.balanceOf(alice), amt);
}再喂给 ChatGPT 或 Copilot,让它自动生成边界异常、法财分离、重入攻击 case。
常见误区:AI 写的“同票不同行”测试需要 人工执行 forge snapshot 找出 gas 异常。FAQ:关于ERC20代币与Foundry的6个高频疑问
Q1:为何必须实现 transferFrom?
A:若未来希望在 DEX 上线(Uniswap/Sushi),自动化做市商需通过 transferFrom 实现流动性注入。
Q2:小数位必须是18吗?
A:不强制。USDC 使用 6 位以小数提升 gas 效率,但多数钱包默认展示 18 位,谨慎选择。
Q3:Foundry与Hardhat的区别?
A:Foundry局部快照、脚本广播、字节码验证更深入链上游;Hardhat JS/TS UI友好、插件丰富。
Q4:可否直接部署到主网?
A:可以,用 forge script,并添加 --verify --etherscan-api-key $ETHERSCAN_API;不过先跑一趟Sepolia更安心。
Q5:自定义稳定币时如何设置升级代理?
A:使用 ERC1967Proxy + ERC20Upgradeable,需额外学习 initializer 模式。
Q6:ERC20代币需要KYC许可吗?
A:协议层无任何限制,交易所上架或向机构销售时需逐一评估合规;务必咨询专业律师。
结业展望:薪资与角色进阶路线
完成上述整套流程,你已拿到 “能交付” 的ERC20代币开发经验,接下来可切入:
| 岗位 | 平均年薪 (USD) |
|---|---|
| 智能合约工程师 | 100k–150k |
| 稳定币协议安全审计 | 100k–200k |
| Web3开发者关系 | 85k–125k |
摘自Cryptocurrency Jobs 2025报告的数据,真实可复刻。
常见扩展与给定项目灵感
- 治理代币:增加
ERC20Votes引入 time-weight 机制 - 跨链桥接:代币与 LayerZero、Wormhole 整合
- 收益聚合器:一次性支持以代币结算的流动性挖矿奖励
下一课预告:《用Foundry做ERC20测试网跨链桥安全审计》,敬请期待!