前言
本文参考了https://eips.ethereum.org/EIPS/eip-20和https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md这两篇文章,这也是官方发布的erc20代币的标准文章。如果我有写的不明白的地方,请大家直接查看官方文档。当然,本文还介绍了如何使用truffle来编译和部署合约。
代币开发
工具概述
开发代币需要使用如下工具Solidity、Remix(或者Truffle + VSCode,VScode是我自己比较喜欢使用的,但是,在开发solidity的时候真的不怎么好使,大家可以使用其他的IDE)、MetaMask。Solidity是EVM的编程语言,Remix(http://remix.ethereum.org/)是IDE(Turffle是一个编译环境,还可以部署合约看大家喜好,本文后边会重点讲解truffle),MetaMask是以太坊钱包(也可以使用其他钱包,反正只要是可以反映自己的货币数量的工具即可)
注意事项
发行一个代币很容易,但是发行一个符合erc20标准的代币就有约束了,我们在注意事项中,将这些约束写一下。
1.solidity的版本必须高于^0.4.17
2.调用方必须处理返回的false(bool success),调用方不能假定永远不会返回false
3.必须保证构建的token可以兼容其他任何交易所和钱包
接口说明
在原文档中,有些接口是可选实现的,但是,为了提高一款代币的可用性,我就将我觉得需要实现的接口都展示在下方了:
接口 | 说明 |
---|---|
name | 代币全称,function name() public view returns (string) |
symbol | 代币简称,function symbol() public view returns (string) |
decimals | 代币精度,function decimals() public view returns (uint8) |
totalSupply | 获取发行的代币总数,function totalSupply() public view returns (uint256) |
balanceOf | 查询账户余额,function balanceOf(address _owner) public view returns (uint256 balance) |
transfer | 代币转账,发送Token到某个地址(转账),注意,0值转移也是正常的业务需求,也将会出发该方法,只有余额不足的情况下,才会报错。function transfer(address _to, uint256 _value) public returns (bool success) |
transferFrom | 从地址from发送token到to地址,与transfer相比,该方法多了一个发送方的参数,该函数其他功能与transfer基本类似。function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) |
approve | 允许_spender从你的账户转出token,function approve(address _spender, uint256 _value) public returns (bool success) |
allowance | 查询允许spender转移的Token数量,function allowance(address _owner, address _spender) public view returns (uint256 remaining) |
事件说明
事件 | 说明 |
---|---|
Transfer | transfer方法调用时的通知事件,当转币发生的时候,提供给其他程序的监听,event Transfer(address indexed _from, address indexed _to, uint256 _value) |
Approval | approve方法调用时的通知事件,委托行为发生时,通过给其他程序的监听,event Approval(address indexed _owner, address indexed _spender, uint256 _value) |
最佳实践
在基本原理弄明白之后,我们就要来实现具体的代码了。在erc20的官方文档中,他们提供了两个可以参考的已经实现的代币源码, OpenZeppelin implementation和 ConsenSys implementation
我们此处就以OpenZeppelin implementation为例为大家讲解一下,如何快速完成代币开发。
OpenZeppelin
OpenZeppelin是一家提供合约审查、安全的公司,再总结个许多代币所存在的问题后,他们开发了这个开源代码源码。使用OpenZeppelin提供的开源代币源码,可以节省我们的大量时间。
准备
eth环境
我准备的是geth,当然大家也可以安装ganache-cli或者使用truffle-dev环境。
安装Truffle
npm i -g truffle
....
....
+ [email protected]
added 91 packages from 305 contributors in 14.517s
root@tumtest:~# truffle version
Truffle v5.0.3 (core: 5.0.3)
Solidity v0.5.0 (solc-js)
构建truffle box编译环境
truffle box提供了开箱即用的solidity开发、编译、部署环境。
mkdir TutorialToken
cd TutorialToken
truffle unbox tutorialtoken
✔ Preparing to download
✔ Downloading
✔ Cleaning up temporary files
✔ Setting up box
Unbox successful. Sweet!
Commands:
Compile: truffle compile
Migrate: truffle migrate
Test contracts: truffle test
Run dev server: npm run dev
安装OpenZeppelin
此处需要使用OpenZeppelin的StandardToken.sol合约,或者说要扩展这个合约。我们先安装OpenZeppelin
npm i zeppelin-solidity
创建代币合约
我们定义的代币合约的名字是TutorialToken,当然,这个名字和目录名没有必然的联系。但是尽量还是保持合约名、目录名、构造函数名统一吧。
我们在/contracts目录下创建TutorialToken.sol智能合约。在此处说明一下,如果有需要定义图标的,需要与imtoken等钱包程序进行沟通与部署,合约中是不建议添加图标的(按字节收费)
pragma solidity ^ 0.4.24;
import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
contract TutorialToken is StandardToken {
string public name = 'TutorialToken';
string public symbol = 'TT';
uint public decimals = 2;
uint public INITIAL_SUPPLY = 100000;
function TutorialToken() public {
totalSupply_ = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
}
添加部署文件
在/migrations目录下,创建文件2_deploy_contracts.js。另外,这个文件夹下还存在一个1_initial_migration.js文件,这个文件是定义合约owner的。2_deploy_contracts.js文件内容如下:
var TutorialToken = artifacts.require("./TutorialToken.sol");
module.exports = function(deployer) {
deployer.deploy(TutorialToken);
};
调整配置文件
在truffle官方文档(https://truffleframework.com/docs/truffle/reference/configuration)说明要将truffle.js转换为truffle-config.js。因此,首先做这样的变换。另外,由于我的truffle对应的默认solidity版本是0.5.0,因此,需要修改版本为0.4.24(OpenZeppelin的代币合约都是使用这个版本的,不修改的话,我这个版本的solidity编译会报错)。配置文件修改完之后如下:
module.exports = {
// See
// for more about customizing your Truffle configuration!
networks: {
development: {
host: "127.0.0.1",
port: 8545,
network_id: "*" // Match any network id
}
},
compilers: {
solc: {
version: "0.4.24"
}
}
};
注意:此处如果不加版本号0.4.24的话,会报错
SyntaxError: Source file requires different compiler version (current compiler is 0.5.0+commit.1d4f565a.Emscripten.clang - note that nightly builds are considered to be strictly less than the released version
编译与部署
部署前记得要设置好部署合约的账户,另外,还要解锁账户我自己的是使用矿工来发布,命令如下:personal.unlockAccount(web3.eth.accounts[0])
truffle compile
truffle migrate
⚠️ Important ⚠️
If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang.
Starting migrations...
======================
> Network name: 'development'
> Network id: *
> Block gas limit: 8000000
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x178eb072152aedb1a3f6ce7a6d80422c9beaf1e4d2e124da9b459e020b27f3aa
> Blocks: 0 Seconds: 0
> contract address: 0x1c35b93DEaD37c25FeB096d8bA2Ac0338e2ef705
> account: 0x0d221f5da9da6C4D2F1AB8030482Ae5FAB004975
> balance: 6643
> gas used: 277462
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00554924 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00554924 ETH
2_deploy_contracts.js
=====================
Deploying 'TutorialToken'
-------------------------
> transaction hash: 0x6856e4dc961507e7e8f2b36672888c47259d3e369c4bfb11b99a899fa9928edf
> Blocks: 4 Seconds: 4
> contract address: 0x7A599aBF7912b910851E75E64e9d3f36025140f8
> account: 0x0d221f5da9da6C4D2F1AB8030482Ae5FAB004975
> balance: 6673
> gas used: 1505388
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.03010776 ETH
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.03010776 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.035657 ETH
其他说明
truffle提供了一个node的服务(在truffle的文件夹下执行npm run dev),大家可以使用这个服务查询合约。但是,我认为此处应该简单说明一下web3的原理,因此,我把web3如何实现查询合约的功能写在下方:
.....
...
..
const ABI = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"}];
const Contract = eth.contract(ABI);
....
..
var Token = Contract.at(contractAddress);
var actualBalance = eth.getBalance(contractAddress);
var totalSupply = Token.totalSupply();
var decimals = Token.decimals();
var name = Token.name();
var symbol = Token.symbol();
此处最重要的是构建ABI。
与ERC20合约交互
truffle console
TutorialToken.deployed().then(instance => contract = instance)
contract.balanceOf('0x0d221f5da9da6C4D2F1AB8030482Ae5FAB004975')
contract.transfer('0x4e0567221aa010636357d2a1a747c3e3723bd9fb', 150000)
//对于有一定精度的转账,可用通过乘法进行
contract.transfer('0x4e0567221aa010636357d2a1a747c3e3723bd9fb', 1000000*2000)
infrua + truffle
eth的infrua提供了节点数据,不需要自己跑节点了。我们可用到infrua官网申请infrua的api。另外,我们还要安装一个npm i truffle-hdwallet-provider
,来管理自己的助记词相关的服务。如果报错了,就先安装个rpc模块npm install ethereumjs-testrpc
最后还要修改truffle.js文件
const HDWalletProvider = require('truffle-hdwallet-provider')
const mnemonic = '333 333 333 333 333 333 333 333 333 333 333 333'
const infura_apikey = '请更换为你自己的apikey'
module.exports = {
networks: {
development: {
host: "127.0.0.1"
, port: 7545
, network_id: "*"
}
, private: {
host: "localhost"
, port: 8545
, network_id: "*"
}
, ropsten: {
provider: new HDWalletProvider(mnemonic, "https://ropsten.infura.io/" + infura_apikey)
, network_id: 3
, gas: 301238
, gasPrice: 30000000000
}, main: {
provider: new HDWalletProvider(mnemonic, "https://mainnet.infura.io/" + infura_apikey)
, network_id: 1
, gas: 3012388
, gasPrice: 1000000000
}
}
}
部署到各个网络的命令
truffle migrate --network development
truffle migrate --network private
truffle migrate --network ropsten
truffle migrate --network main
总结
本文介绍了构建一个满足erc20合约的代币需要做什么事情以及注意事项。并通过使用最佳实践的方式快速的开发了一个安全而又满足erc20标准的代币。我们可以结合web3对合约进行查询。