简介

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":代币简称,类似比特币称为BTC
  • uint8 public decimals = 4:最小分割单位,即一个代币允许最小分割成10的多少次方份,比特币是8,以太币是18
  • uint256 public INITIAL_SUPPLY = 666666:初始发行数量
import "zeppelin-solidity/contracts/token/ERC20/StandardToken.sol";

这句代码是通过import来导入我们需要使用到的StandardToken合约。

contract TestWorldCoin is StandardToken {
    ...
}

建立TestWorldCoin合约时,让TestWorldCoin合约直接继承自StandardTokenis既是继承。因此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命令,安装完成后在命令行中输入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码,也就是返回信息中的txRopsten官网上去查询这笔交易的信息。

使用在线IDE:Remix部署

Remix是一个功能强大的开源工具,使用它能直接在浏览器编写Solidity合约。RemixJavaScript编写,支持在浏览器和本地使用。

使用Remix可以在线的进行Solidity智能合约的编写、编译、调试、部署等。这里我们项目的创建和代币合约的编写还是和上一种部署方式一样,使用truffle来构建,构建完代币合约之后,剩下的编译部署工作再转移到Remix来。

Remix在线IDE地址:https://remix.ethereum.org/

Remix官方文档

部署前准备

安装MateMask

安装MateMask轻钱包,ChromeFirefox都有其插件拓展,Chrom点击下载:MetaMask Chrom插件拓展

安装完MateMask插件之后,打开并创建钱包账户,然后将网络切换到Ropsten测试网络:

这时候可以看到Remix里检测到了当前钱包的网络环境和钱包账户地址:

链接本地文件

虽然也可以在Remix上面编写合约,但是毕竟Remix是在线的IDE,不便于项目管理,所以我选择在本地先用truffle生成相关项目结构、依赖,然后在使用Remix提供的remixd模块链接本地文件夹,进行编译和部署。

要在Remix上读取本地文件,需要使用remixd模块。

第一步,安装remixd模块,打开命令行,使用如下命令全局安装:

npm install -g remixd

安装此模块需要python支持,如果没有安装Python,先安装python并配置一下环境变量,需要注意的是,这边需要安装python2.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测试网络:

点击右上方的RunEnvironment选择Injected Web3即注入到Web3,然后点击右侧中的Deploy部署按钮进行合约部署。

这时MetaMask会跳出来合约部署信息要求确认,可以看到部署合约需要消耗多少gas等等信息,点击确认:

这时候合约就开始部署啦,部署完成可以看到控制台会输出这笔合约部署的交易信息,交易的哈希码、花费了多少gas等等,在右侧边栏可以看到合约基于ERC20协议提供的一些基本的查询、转账等方法,可以直接进行调用:

在右侧边栏合约方法上面那一串就是合约地址,复制出来,在MetaMask钱包里面选择添加代币,输入合约地址,即可看到当前账户拥有的我们发行的代币信息:

至此,使用Remix进行合约部署就完成啦。