简介
OpenZeppelin
是一套能够给我们方便提供编写加密合约的函数库,同时里面也提供了兼容ERC20
的智能合约,通过OpenZeppelin
可以简化加密代币开发的过程。
至于Truffle
,是以太坊Solidity编程语言开发框架,在之前的文章中介绍过基本使用方式:以太坊Solidity编程语言开发框架:Truffle基本使用方式。
构建项目
创建项目文件夹并进入项目文件夹下:
mkdir TWC
cd TWC
使用npm init
命令创建项目配置,过程中出现交互式设置项目信息回车即可:
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.
Press ^C at any time to quit.
package name: (twc) twc
version: (1.0.0)
description:
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)
About to write to E:\Project\VS Code\TWC\package.json:
{
"name": "twc",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
Is this ok? (yes)
通过truffle
初始化项目,控制台中使用truffle init
命令,初始化成功可见如下信息:
√ 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
安装OpenZeppelin
项目目录下,使用npm install zeppelin-solidity
命令,成功可见如下提示信息:
npm WARN deprecated zeppelin-solidity@1.12.0: This package has been renamed to openzeppelin-solidity. Please update your dependency, or you will no longer receive updates.
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN twc@1.0.0 No description
npm WARN twc@1.0.0 No repository field.
+ zeppelin-solidity@1.12.0
added 1 package in 2.525s
此时,项目目录下会生成node_modules
目录,里面即可看到OpenZeppelin
依赖包信息。
创建代币合约
在contracts/
目录下建立一个TestWorldCoin.sol
文件,并写入如下代码:
pragma solidity ^0.4.24;
import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
contract TestWorldCoin is StandardToken {
string public name = "TestWorldCoin";
string public symbol = "TWC";
uint8 public decimals = 4;
uint256 public INITIAL_SUPPLY = 666666;
uint256 public totalSupply;
function TestWorldCoin() {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
}
代码解释
string public name = "TestWorldCoin"
:代币全称string public symbol = "TWC"
:代币简称,类似比特币称为BTCuint8 public decimals = 4
:最小分割单位,即一个代币允许最小分割成10的多少次方份,比特币是8,以太币是18uint256 public INITIAL_SUPPLY = 666666
:初始发行数量
import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";
这句代码是通过import
来导入我们需要使用到的StandardToken
合约。
contract TestWorldCoin is StandardToken {
...
}
建立TestWorldCoin
合约时,让TestWorldCoin
合约直接继承自StandardToken
。is
既是继承。因此TestWorldCoin
继承了StandardToken
所有的状态数据和方法。
当我们继承了StandardToken
合约,也就支持了以下ERC20
标准中规定的函数。
函数 | 方法 |
---|---|
totalSupply() | 代币发行的总量 |
balanceOf(A) | 查询A帐户下的代币数目 |
transfer(A,x) | 发送x个代币到A帐户 |
transferFrom(A,x) | 从A帐户提取x个代币 |
approve(A,x) | 同意A帐户从我的帐户中提取x个代币 |
allowance(A,B) | 查询B帐户可以从A帐户提取多少代币 |
function TestWorldCoin() {
totalSupply = INITIAL_SUPPLY;
balances[msg.sender] = INITIAL_SUPPLY;
}
和合约同名的TestWorldCoin
方法,就是TestWorldCoin
合约的构造函数(constructor)
。在构造函数里指定了totalSupply
数目,并将所有的初始代币INITIAL_SUPPLY
都指定给msg.sender
帐户,也就是用来部署这个合约的帐户。totalSupply
定义于ERC20Basic.sol
中,balances
定义于BasicToken.sol
中。
部署
部署的方式有很多种,这里主要介绍使用truffle和
Remix在线IDE进行部署。
使用Truffle部署
编译
在migrations/
目录下建立一个2_deploy_TestWorldCoin.js
文件,并写入如下代码:
var TestWorldCoin = artifacts.require("./TestWorldCoin.sol");
module.exports = function(deployer) {
deployer.deploy(TestWorldCoin);
};
控制台中使用truffle compile
命令即可编译项目。
如果出现编译失败:源文件需要不同的编译器版本的错误,编辑项目目录下的truffle-config.js
文件,去掉compilers→solc
里的version
配置的注释,使其生效,并修改成相应的solidity版本,我使用的是0.4.24
,修改完成后重新进行编译。
部署前准备
安装Geth客户端
Go Ethereum
是以太坊协议的三个原始实现之一(另外两个是C ++和Python),提供了强大的命令行工具,在开发中经常需要使用到geth
,接下来的部署就需要用到。
这里暂时不需要用到其他太多的geth
命令,安装完成后在命令行中输入geth version
确认安装成功后即可进行下一步。
部署配置
在项目根目录下有一个truffle-config.js
配置文件,之前的truffle版本也叫truffle.js
,修改其中配置:
去掉以下4行代码的注释,使其生效,并修改成自己的信息:
// const HDWalletProvider = require('truffle-hdwallet-provider');
// const infuraKey = "fj4jll3k.....";
// const fs = require('fs');
// const mnemonic = fs.readFileSync(".secret").toString().trim();
第一行代码是加载truffle-hdwallet-provider
模块,truffle-hdwallet-provider
的作用是读取助记词
对事务进行签名并提交到web3
,助记词创建钱包账户时会生成,如果还没有使用创建过钱包账户,推荐使用MateMask
轻钱包,chrome和Firefox都有其浏览器插件,Chrom
点击下载:MetaMask Chrom插件拓展。使用truffle-hdwallet-provider
还需要引入相关依赖,打开命令行窗口进入到项目目录,使用npm install truffle-hdwallet-provider
命令下载相关依赖包。
第二行代码是设置infuraKey
的值,infuraKey
是在infura
上创建的项目ID,可以理解成使用infura
的凭证,infura
是一个托管的以太坊节点集群,通过它,我们可以将自己的合约部署在托管集群上,从而无需在本地部署自己的以太坊节点。infuraKey
的获取需要先到infura官网注册,注册成功后创建自己的项目,项目创建后生成的PROJECT ID
就是infuraKey
,复制到代码中替换默认的值即可。
三四行代码的作用是使用fs
模块读取钱包助记词。在项目目录下创建key.txt
,并将你的钱包助记词复制进去,将".secret"
修改成"key.txt"
。当然,你也可以不使用fs
模块,直接将你的钱包助记词以字符串形式赋值给mnemonic
。
修改后的代码:
const HDWalletProvider = require('truffle-hdwallet-provider');
const infuraKey = "你的infura项目ID,如:7a95cb81610d6c99bb8f73a05569add5";
const fs = require('fs');
const mnemonic = fs.readFileSync("key.txt").toString().trim();
接下来需要修改的是部署时的网络配置,定义合约将部署到哪一个以太坊网络上,以太坊目前除了主网,还在运行的还有三条测试网络,关于三条测试网络的区别以及测试以太币的获取参见:以太坊测试网络区别以及获取测试币方法
这里选择将合约部署到ropsten
测试网络上,去掉配置文件中,networks
配置项里ropsten
配置的注释,使其生效:
ropsten: {
provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/${infuraKey}`),
network_id: 3, // Ropsten's id
gas: 5500000, // Ropsten has a lower block limit than mainnet
confirmations: 2, // # of confs to wait between deployments. (default: 0)
timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50)
skipDryRun: true // Skip dry run before migrations? (default: false for public nets )
}
部署到Ropsten测试网络
项目目录下使用truffle migrate --network ropsten
命令将代币合约部署到ropsten
测试网络,部署合约的账户地址默认是配置文件中读取的助记词
对应的钱包里的第一个账户,部署时确保该账户中有足够的测试以太币,部署成功后发行的代币也是默认发送到这个账户地址中,部署成功可见如下提示信息:
⚠️ Important ⚠️
If you're using an HDWalletProvider, it must be Web3 1.0 enabled or your migration will hang.
Starting migrations...
======================
> Network name: 'ropsten'
> Network id: 3
> Block gas limit: 8000000
1_initial_migration.js
======================
Deploying 'Migrations'
----------------------
> transaction hash: 0x0ab4e7a8b262718172f7c199108722fc808305db158394f4c1d51360f4014de5
> Blocks: 0 Seconds: 33
> contract address: 0x45122E712f7AD3744A3b6a32169c57E337770347
> account: 0xC694EBE5A45aD0AccD694BBb9F5c0dA8c61a6181
> balance: 0.99445076
> gas used: 277462
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.00554924 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 4854295)
> confirmation number: 2 (block: 4854296)
> Saving migration to chain.
> Saving artifacts
-------------------------------------
> Total cost: 0.00554924 ETH
2_deploy_TestWorldCoin.js
=========================
Deploying 'TestWorldCoin'
-------------------------
> transaction hash: 0xb5394050b523631e5954f0582118fb730be5fd9306e525acd36e7a63efd69336
> Blocks: 1 Seconds: 25
> contract address: 0x3e6b268Ca744bBa8aEcBaE57a0Bb3f12d474d6d5
> account: 0xC694EBE5A45aD0AccD694BBb9F5c0dA8c61a6181
> balance: 0.96338586
> gas used: 1511237
> gas price: 20 gwei
> value sent: 0 ETH
> total cost: 0.03022474 ETH
Pausing for 2 confirmations...
------------------------------
> confirmation number: 1 (block: 4854302)
至此,使用truffle部署代币合约到即完成,在钱包里添加代币指向这个合约地址即可看到相应账户中代币的数量,也可以拿着合约地址与合约进行转账查询等互动。
与合约互动
Truffle
控制台中获取合约实例:
var twc = TestWorldCoin.deployed().then(instance => twc = instance);
转账:
twc.transfer('0x4D6E19EE19D1A08a05F0Aa5c6069b28A9B91188e',666666);
成功返回如下交易信息:
{ tx: '0x216b96647e0dce4b6183c2185aad930fe941151e4e4a742e2530a62b5b3c64f5',
receipt:
{ blockHash: '0xc9bc80a7f5c346a35aa5e8ef7fc3148033438c10e7dd32670b21b9d60eb0aa46',
blockNumber: 4854391,
contractAddress: null,
cumulativeGasUsed: 21633,
from: '0xc694ebe5a45ad0accd694bbb9f5c0da8c61a6181',
gasUsed: 21633,
logs: [ [Object] ],
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000080000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000040000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000002000000000000000000000000020000000000000000000000000000008000000000000000400000000000000000000002000000000000000000080000',
status: true,
to: '0x3e6b268ca744bba8aecbae57a0bb3f12d474d6d5',
transactionHash: '0x216b96647e0dce4b6183c2185aad930fe941151e4e4a742e2530a62b5b3c64f5',
transactionIndex: 0,
rawLogs: [ [Object] ] },
logs:
[ { address: '0x3e6b268Ca744bBa8aEcBaE57a0Bb3f12d474d6d5',
blockHash: '0xc9bc80a7f5c346a35aa5e8ef7fc3148033438c10e7dd32670b21b9d60eb0aa46',
blockNumber: 4854391,
logIndex: 0,
removed: false,
transactionHash: '0x216b96647e0dce4b6183c2185aad930fe941151e4e4a742e2530a62b5b3c64f5',
transactionIndex: 0,
id: 'log_ca7ded94',
event: 'Transfer',
args: [Object] } ] }
可以拿着这笔交易的hash码,也就是返回信息中的tx
到Ropsten官网上去查询这笔交易的信息。
使用在线IDE:Remix部署
Remix是一个功能强大的开源工具,使用它能直接在浏览器编写Solidity
合约。Remix用JavaScript
编写,支持在浏览器和本地使用。
使用Remix可以在线的进行Solidity
智能合约的编写、编译、调试、部署等。这里我们项目的创建和代币合约的编写还是和上一种部署方式一样,使用truffle
来构建,构建完代币合约之后,剩下的编译部署工作再转移到Remix来。
Remix在线IDE地址:https://remix.ethereum.org/
部署前准备
安装MateMask
安装MateMask
轻钱包,Chrome
和Firefox
都有其插件拓展,Chrom
点击下载:MetaMask Chrom插件拓展。
安装完MateMask
插件之后,打开并创建钱包账户,然后将网络切换到Ropsten
测试网络:
这时候可以看到Remix里检测到了当前钱包的网络环境和钱包账户地址:
链接本地文件
虽然也可以在Remix上面编写合约,但是毕竟Remix是在线的IDE,不便于项目管理,所以我选择在本地先用truffle
生成相关项目结构、依赖,然后在使用Remix提供的remixd
模块链接本地文件夹,进行编译和部署。
要在Remix上读取本地文件,需要使用remixd
模块。
第一步,安装remixd
模块,打开命令行,使用如下命令全局安装:
npm install -g remixd
安装此模块需要python
支持,如果没有安装Python
,先安装python
并配置一下环境变量,需要注意的是,这边需要安装python
2.7版本的,安装3.X版本是不行的:Python 2.7版本官网下载地址。
remixd
模块安装完成后,使用以下命令启动并设置链接文件路径:
remixd -s 你的项目本地路径 --remix-ide https://remix.ethereum.org
第一次启动报了个错误:
Error: Cannot find module './build/Release/scrypt'
at Function.Module._resolveFilename (module.js:547:15)
at Function.Module._load (module.js:474:25)
at Module.require (module.js:596:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (C:\Users\wu081\AppData\Roaming\npm\node_modules\remixd\node_modules\scrypt\index.js:3:20)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Module.require (module.js:596:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (C:\Users\wu081\AppData\Roaming\npm\node_modules\remixd\node_modules\scrypt.js\node.js:1:76)
at Module._compile (module.js:652:30)
at Object.Module._extensions..js (module.js:663:10)
at Module.load (module.js:565:32)
at tryModuleLoad (module.js:505:12)
at Function.Module._load (module.js:497:3)
at Module.require (module.js:596:17)
at require (internal/module.js:11:18)
at Object.<anonymous> (C:\Users\wu081\AppData\Roaming\npm\node_modules\remixd\node_modules\web3-eth-accounts\src\index.js:35:16)
at Module._compile (module.js:652:30)
根据错误提示信息,查看C:\Users\wu081\AppData\Roaming\npm\node_modules\remixd\node_modules\scrypt.js\node.js
文件,发现其引入的./build/Release/scrypt
文件目录并不存在,反而是同级目录下存在一个scrypt
目录。
所以尝试修改C:\Users\wu081\AppData\Roaming\npm\node_modules\remixd\node_modules\scrypt.js\node.js
文件第三行代码,从:
var scryptNative = require("./build/Release/scrypt")
改成:
var scryptNative = require("scrypt")
再次启动,启动成功,可看到如下提示:
[WARN] You may now only use IDE at https://remix.ethereum.org to connect to that instance
[WARN] Any application that runs on your computer can potentially read from and write to all files in the directory.
[WARN] Symbolinc links are not forwarded to Remix IDE
setup notifications for E:\WJXC
Tue Jan 22 2019 23:40:23 GMT+0800 (中国标准时间) Remixd is listening on 127.0.0.1:65520
这时返回Remix在线IDE,点击左上角的链接图标,点击连接:
连接成功可以看到那个链接的小图标变成了绿色,左边的文件目录多出来一个localhost文件夹。
编译
点击localhost文件夹,打开我们的代币合约文件,在右侧的编译设置选择要使用的编译器版本,IDE会自动进行编译,编译成功或失败右下角会有提示信息:
部署
打开MetaMask
钱包,选择网络节点和要进行合约部署的账户,这里我要部署到Ropsten
测试网络:
点击右上方的Run
,Environment
选择Injected Web3
即注入到Web3
,然后点击右侧中的Deploy
部署按钮进行合约部署。
这时MetaMask
会跳出来合约部署信息要求确认,可以看到部署合约需要消耗多少gas
等等信息,点击确认:
这时候合约就开始部署啦,部署完成可以看到控制台会输出这笔合约部署的交易信息,交易的哈希码、花费了多少gas
等等,在右侧边栏可以看到合约基于ERC20
协议提供的一些基本的查询、转账等方法,可以直接进行调用:
在右侧边栏合约方法上面那一串就是合约地址,复制出来,在MetaMask
钱包里面选择添加代币,输入合约地址,即可看到当前账户拥有的我们发行的代币信息:
至此,使用Remix进行合约部署就完成啦。