基于以太坊 ETH 的智能合约学习(二)使用官方的 Remix IDE 一键发币体验整体流程

从零开始学习 Solidity 语言的时候突然回想起上大学对着键盘敲 C 的日子,老师明知道台下一半的学生出了校园甚至是这门课结束了就再也不会碰 C,却依然很负责任的教书,不甚唏嘘。
本来想从第二章就介绍以太坊链的一些专有名词、Solidity 的语法的,考虑了一下还是先一起走一遍发币流程吧,也不会浪费太多时间,而且只想浅尝辄止体验一下的读者学完本章也能如愿了。


参考教程:10 分钟发行自己的加密货币,零基础教学 | 2021 (ETH, BTC)
本章的内容也是对上述视频的实践和拓展(除了 MetaMask 钱包那部分,已经在上一章详细讲过了),我推荐先看一遍视频来了解我们总共需要做哪些事情。

1、明确我们要做什么
虽然标题为一键发币看起来非常方便非常简单,但在这之前还是要理一下“发币”的动作如何实现,这对你之后的操作会很有帮助。
很简单,实现方式就是在以太坊链上发布一份智能合约,而这份合约内的代码要包含以下两个功能即可:

  1. 根据我对其名字、数量等信息的设定而生成代币
  2. 将这些代币全转到我的钱包内

而代码在哪里、如何运行就不用你操心了,至少在这章不需要,全交给以太坊虚拟机即可。

2、切换至以太坊测试链并领取测试用 ETH
由于暂时挖的 ETH 还不够部署合约,因此这里使用官方提供的 Ropsten 测试链,之后的发币操作也是在这条链上执行的。
在 MetaMask 上显示测试链:
添加网络
高级
Show test networks
之后切换到 Ropsten 测试网络:
Ropsten 测试网络
前往 https://faucet.metamask.io 领取免费的测试用 ETH,点击 request 1 ether from faucet 按钮后连接 MetaMask 钱包:
连接 MetaMask 钱包
连接完成后就能看到下方的的交易记录了,一枚测试用以太坊就打入了你的钱包:
交易记录
测试用以太坊

如果领取失败的话,尝试关闭代理或使用私人代理,机场节点的 IP 一般都无法领取。
视频教程中的 https://faucet.ropsten.be 领取站点的钱包在我进行操作时已经没有 ETH 余额了,所以没用。

3、打开 Remix 智能合约开发环境并克隆现成的代币合约代码
像写 Java 你会用 IDEA、写 Python 我会用 VS Code 一样,智能合约也有适合它的 IDE:Remix
智能合约的运行依赖 Solidity 环境,如果你想用 VS Code 写的话当然也行,不过需要花费很多时间来安装 Solidity 的编译环境,Solidity 官方是推荐使用 Remix 进行合约开发的:
官方推荐
且以太坊官方也提供了在线 Remix:remix.ethereum.org,那么我这里就直接用了。

如果你想在自己的云服务器上安装 Remix 的话可以看我这篇文章:基于以太坊 ETH 的智能合约学习(二) 补足:CentOS7 下使用 Docker 安装 remix-ide 智能合约开发环境

访问 https://remix.ethereum.org
访问 remix.ethereum.org
选择从 GitHub 导入代码:
从 GitHub 导入代码
这里用到的是这个仓库:ConsenSys/Tokens
而合约最主要的两份代码是:
主实现代码:https://github.com/ConsenSys/Tokens/blob/fdf687c69d998266a95f15216b1955a4965a0a6d/contracts/eip20/EIP20.sol
接口类:https://github.com/ConsenSys/Tokens/blob/fdf687c69d998266a95f15216b1955a4965a0a6d/contracts/eip20/EIP20Interface.sol
都导入到项目中:
导入
导入完成

参考以太坊 ERC20 Token 合约代码分析一文,稍微对主实现代码做下解释:

/*
Implements EIP20 token standard: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
.*/


pragma solidity ^0.4.21;

import "./EIP20Interface.sol";


contract EIP20 is EIP20Interface {

    uint256 constant private MAX_UINT256 = 2**256 - 1;
    mapping (address => uint256) public balances;
    mapping (address => mapping (address => uint256)) public allowed;
    /*
    NOTE:
    The following variables are OPTIONAL vanities. One does not have to include them.
    They allow one to customise the token contract & in no way influences the core functionality.
    Some wallets/interfaces might not even bother to look at this information.
    */
    // Token 的必填属性:Token 名称、小数位数(一般为 18 位)和 Token 的简称
    string public name;                   //fancy name: eg Simon Bucks
    uint8 public decimals;                //How many decimals to show.
    string public symbol;                 //An identifier: eg SBX
    
    function EIP20(
        // 部署合约时需要填入的变量
        uint256 _initialAmount,
        string _tokenName,
        uint8 _decimalUnits,
        string _tokenSymbol
    ) public {
        // 将用户输入的变量实际赋值给合约
        // 合约创建者默认拥有所有的 Token(将币的总数赋予合约创建者的地址,这样在你添加这个 Token 种类后,币就全在你钱包里了)
        balances[msg.sender] = _initialAmount;               // Give the creator all initial tokens
        // 发行总量,要根据小数点精确度来计算,比如发行 100 个,小数点后位数为 2,则需要填写 10000
        totalSupply = _initialAmount;                        // Update total supply
        name = _tokenName;                                   // Set the name for display purposes
        decimals = _decimalUnits;                            // Amount of decimals for display purposes
        symbol = _tokenSymbol;                               // Set the symbol for display purposes
    }

    // 发送 Token 的动作(一般由合约创建者执行)
    function transfer(address _to, uint256 _value) public returns (bool success) {
        require(balances[msg.sender] >= _value);
        balances[msg.sender] -= _value;
        balances[_to] += _value;
        emit Transfer(msg.sender, _to, _value); //solhint-disable-line indent, no-unused-vars
        return true;
    }

    // 转账 Token 的动作(一般由合约创建者执行)
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
        // 获取 Token 来源方授权交易的总数
        uint256 allowance = allowed[_from][msg.sender];
        require(balances[_from] >= _value && allowance >= _value);
        balances[_to] += _value;
        balances[_from] -= _value;
        if (allowance < MAX_UINT256) {
            allowed[_from][msg.sender] -= _value;
        }
        emit Transfer(_from, _to, _value); //solhint-disable-line indent, no-unused-vars
        return true;
    }

    function balanceOf(address _owner) public view returns (uint256 balance) {
        return balances[_owner];
    }

    function approve(address _spender, uint256 _value) public returns (bool success) {
        allowed[msg.sender][_spender] = _value;
        emit Approval(msg.sender, _spender, _value); //solhint-disable-line indent, no-unused-vars
        return true;
    }

    function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
        return allowed[_owner][_spender];
    }
}

4、编译代码
点击左侧的 Solidity compiler 按钮,并选择代码指定版本的编译器,点击编译:
编译
看到小绿钩出现代表编译完成了:
编译完成

5、部署合约
点击左侧的 Deploy 按钮,并检查你的 MetaMask 钱包当前是测试网络下的:
Deploy
环境选择 Injected Web3 来连接 MetaMask 钱包:
连接 MetaMask 钱包
MetaMask 钱包允许连接
连接完成后你就是合约创建者了:
连接完成
其他的油费之类的不用管,直接做合约的进阶配置,还记得我在前面源码中标了注释的参数吗?就是要填那些!
展开:
展开
参数和其解释:
选项

参数 解释 样例
_INITIALAMOUNT 发行总量(需要计算小数点位数)。 10000(100 个币,小数点后为 2 位)
_TOKENNAME 币的名称。 SENJIANLU COIN
_DECIMALUNITS 小数点位数。 2
_TOKENSYMBOL 币的简称。 SEN

填写完成,确定没问题后点击 transact 按钮:
点击 transact 按钮
MetaMask 钱包会弹出并提示你部署这个合约需要的费用(没弹出的话请手动点击钱包):
合约费用
确认之后,稍等片刻看见钱包内余额少了,说明部署成功了:
部署成功

Remix 的控制台内也会打印成功信息:
Remix 成功信息

点击 MetaMask 中的 活动
活动
点进刚刚的合约中,拉到下面点进最近的一条日志中:
最近的一条日志
这里的 To 就是你创建的智能合约(发的币)的地址了,复制一下:
合约地址
把这个合约地址(可以理解为币的种类)导入钱包即可:
Import tokens
复制合约地址
成功:
成功
说一句,转账币的操作消耗的是发起者的 ETH,因此玩的话还请留点测试 ETH 在钱包中。

结束。