以太坊发币 - 超简单发行 ERC-20 代币并上线到 holesky 上

ERC20是什么

ERC20 实际上就是一个用智能合约发行代币的标准。本质上就是用智能合约的代码来规定好这个币的总量有多少,某个地址拥有多少币。

ERC20 代币的规范

Openzeppelin 中,与 ERC20 代币相关的接口有两个,IERC20IERC20MetadataIERC20 负责代币的行为抽象,IERC20Metadata 负责代币的表现抽象。下面,我们来逐一分析 IERC20IERC20Metadata 的接口。

IERC20Metadata

image.png - 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
  1. 编写 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);
    }
}
  1. 编写测试 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

image.png 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();
    }
}
  1. 配置环境变量
    • 采用 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"
  1. 执行部署命令 在终端中,切换到项目的根目录文件夹下,执行命令
source .env
forge script script/ERC20/DeployShawnERC20.sol:DeployShawnERC20 \
  --rpc-url $HOLESKY_RPC_URL \
  --private-key $PRIVATE_KEY \
  --broadcast \
  -vvvv
  • 你将会看到

image.png

  • 拿着这个合约地址(0xBc0112bcd06094A18432Fa3610C77c75553c1a0c),我们可以去区块浏览器查看交易记录。 -区块浏览器

image.png

至此,合约已部署上线,后续可通过调用完成 ERC20 代币的授权、转移。

全部评论(0)