ERC20是什么
ERC20
实际上就是一个用智能合约发行代币的标准。本质上就是用智能合约的代码来规定好这个币的总量有多少,某个地址拥有多少币。
ERC20 代币的规范
在 Openzeppelin
中,与 ERC20
代币相关的接口有两个,IERC20
和 IERC20Metadata
。IERC20
负责代币的行为抽象,IERC20Metadata
负责代币的表现抽象。下面,我们来逐一分析 IERC20
和 IERC20Metadata
的接口。
IERC20Metadata
- name():负责返回该代币的名称
- symbol():负责返回该代币的标志
- decimal():负责返回该代币的精度
IERC20
- 事件
// 发生转账时,该事件会被释放
event Transfer(address indexed from, address indexed to, uint256 value);
// 发生代币授权时,该事件会被触发
event Approval(address indexed owner, address indexed spender, uint256 value);
- 函数
// 返回该代币的总供应量
function totalSupply() external view returns (uint256);
// 返回某个地址持有该代币的量
function balanceOf(address account) external view returns (uint256);
// 从本调用者地址转到 to 地址
function transfer(address to, uint256 value) external returns (bool);
// 返回owner授权给 spender 的代币额度
function allowance(address owner, address spender) external view returns (uint256);
// 调用者授权给 spender amount 单位的代币
function approve(address spender, uint256 value) external returns (bool);
// 从 from 地址转出 value 单位的代币到 to 地址,走的是授权机制(需 approve)
function transferFrom(address from, address to, uint256 value) external returns (bool);
手撸一个 ERC20 代币并上线
使用 foundry
开发、测试、部署一条龙服务
安装 Openzeppelin
的依赖库
forge install OpenZeppelin/openzeppelin-contracts
- 编写 ShawnERC20.sol
contract ShawnERC20 is IERC20, IERC20Metadata {
// 记录某个地址的代币数量
mapping(address => uint256) public override balanceOf;
// 记录某个地址授权给另一个地址的代币数量
mapping(address => mapping(address => uint256)) public override allowance;
// 总供应量
uint256 public override totalSupply;
string public override name = "Shawn Token";
string public override symbol = "SC";
uint8 public override decimals = 10;
constructor(string memory name_, string memory symbol_){
name = name_;
symbol = symbol_;
}
// 从 msg.sender 转到 to
function transfer(address to, uint256 value) external returns (bool){
// msg.sender 扣减
balanceOf[msg.sender] -= value;
// to 加上
balanceOf[to] += value;
emit Transfer(msg.sender,to,value);
return true;
}
// 授权,只做记录,未发生实际转账
function approve(address spender, uint256 value) external returns (bool){
// 记录 msg.sender 授权给 spender value 数量的代币
allowance[msg.sender][spender] = value;
emit Approval(msg.sender,spender, value);
return true;
}
// 授权转账
function transferFrom(address from, address to, uint256 value) external returns (bool){
// from 从记录中扣减 value 数量的代币
allowance[from][msg.sender] -= value;
// 实际 from 的余额 扣减
balanceOf[from] -= value;
// to 的实际余额增加
balanceOf[to] += value;
emit Transfer(from, to, value);
return true;
}
// 铸币(当前任何人都能铸币,实际应加上权限控制)
function mint(uint amount) external {
// 给 sender 加上币
balanceOf[msg.sender] += amount;
// 供应量增加
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
// 燃烧
function burn(uint amount) external {
// sender 扣减
balanceOf[msg.sender] -= amount;
// 供应量扣减
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
- 编写测试 ShawnERC20Test.sol(可以不写,直接部署也行)
contract ShawnERC20Test is Test {
ShawnERC20 token;
address alice = address(1);
address bob = address(2);
function setUp() public {
token = new ShawnERC20("Shawn Token", "SC");
}
function testMint() public {
vm.prank(alice);
token.mint(10);
assertEq(token.totalSupply(), 10);
assertEq(token.balanceOf(alice), 10);
}
function testTransfer() public {
vm.prank(alice);
token.mint(10);
vm.prank(alice);
token.transfer(bob, 3);
assertEq(token.balanceOf(alice), 7);
assertEq(token.balanceOf(bob), 3);
}
function testApproveAndTransferFrom() public {
vm.prank(alice);
token.mint(10);
vm.prank(alice);
token.approve(bob, 3);
assertEq(token.allowance(alice, bob), 3);
vm.prank(bob);
token.transferFrom(alice, bob, 2);
assertEq(token.balanceOf(bob), 2);
assertEq(token.balanceOf(alice), 8);
assertEq(token.allowance(alice, bob), 1);
}
function testBurn() public {
vm.prank(alice);
token.mint(10);
vm.prank(alice);
token.burn(8);
assertEq(token.balanceOf(alice), 2);
assertEq(token.totalSupply(), 2);
}
}
- 执行测试命令
forge test --match-contract ShawnERC20Test -v
3. 编写部署 DeployShawnERC20.sol
contract DeployShawnERC20 is Script {
function run() external {
vm.startBroadcast();
// 部署 ERC20 合约
ShawnERC20 token = new ShawnERC20("Shawn Token", "SC");
// mint 给部署者 100 SC
token.mint(100 ether);
// 定义其他地址
address to = address(0xBEEF); // 接收地址
address spender = address(0xCAFE); // 被授权地址
// 发送 10 SC 给 to
token.transfer(to, 10 ether);
// burn 10 SC(从 msg.sender 地址销毁)
token.burn(10 ether);
vm.stopBroadcast();
}
}
- 配置环境变量
- 采用
holesky
测试网络进行合约部署 - 在项目的根目录下,建立一个
.env
文件,配置部署合约的私钥和交易的rpc
节点 - 如果你的私钥相应账号上没有
ETH
,可以先去水龙头领取免费的ETH
google cloud 水龙头
- 采用
PRIVATE_KEY=0x_your_private_key
HOLESKY_RPC_URL=https://ethereum-holesky-rpc.publicnode.com
- 在
foundry.toml
文件中,也进行配置holesky
网络rpc
节点
[rpc_endpoints]
holesky = "https://ethereum-holesky-rpc.publicnode.com"
- 执行部署命令 在终端中,切换到项目的根目录文件夹下,执行命令
source .env
forge script script/ERC20/DeployShawnERC20.sol:DeployShawnERC20 \
--rpc-url $HOLESKY_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
-vvvv
- 你将会看到:
- 拿着这个合约地址(
0xBc0112bcd06094A18432Fa3610C77c75553c1a0c
),我们可以去区块浏览器查看交易记录。 -区块浏览器
至此,合约已部署上线,后续可通过调用完成 ERC20
代币的授权、转移。