Truffle部署Solidity合约教程(etherscan多文件自动开源)

前言

最近一直在折腾 Solidity 合约的学习,虽然现在普通的合约代码能看懂了,但是一直好奇如何把合约部署到链上。

最开始学习的方式是通过官方 Remix 的网站进行部署,单文件还好说,多文件开源起来非常麻烦。

后来得知了两个部署框架,一个是以 Python 为主语言的开发框架 Brownie ,还有一个是以 Js 为主语言的开发框架 Truffle ,还有是 Js 的一个 Hardhat,还没有尝试用过。

Truffle 和 Brownie 在流程体验上,可以说学会了一个,另一个也大概就明白什么意思了,但得益于 Truffle 初始化工程项目后,生成的配置文件 truffle-config.js 比 Brownie 默认的配置文件友好许多。

如下,起码会有很多默认的注释掉的配置,让新手看起来就通俗易懂:

所以,分享下今天一天的踩坑经验,就来讲讲如何把一个合约通过 Truffle 从零部署到测试网上,同时使用代码自动验证开源,不需要手动去 etherscan 去复制代码,贴代码进行验证发布并开源。

正文

一、初始化工程

这里的安装依赖,假设你已经安装好了 nodejs 。

1、通过 nodejs 安装 truffle 框架:

// 1. 安装 truffle
npm install -g truffle

2、新建项目,切到项目目录下,执行 truffle init:

 // 2. 使用 init 命令对项目进行初始化 
truffle init

我这里新建了一个目录,叫 my-first-truffle,执行完命令后,目录如下:

但一般当我们编译 solidity 文件和引入依赖后,往往项目会变成这样:

解释:

--build: 存放编译后文件的目录
--contracts: solidity合约代码的目录
--migrations: 用 js 语言写的部署用的文件
--node_modules: js的项目依赖
--test: 用 js 语言写的单元测试目录
  --package.json: 依赖的配置文件
  --truffle-config.js: truffle框架的配置文件 以上就是一个新工程初始化后的样子。

别忘记,回到命令行,对 npm 进行工程初始化,我这里用的 cnpm ,cnpm init,然后疯狂回车,一路默认即可:

二、编写用例合约

我这里编写一个自己的 token 用例,这里继承了 ERC20 ,为的就是让合约文件本身有 import 的操作,这样便可以验证在后面的 truffle 自动部署并且验证开源到 etherscan 上。

代码注释写的很清楚了,有基础的自然看的懂,代码不是本文的重点,部署流程才是。

新建一个合约文件 DaMiToken.sol ,新建一个部署合约的 js 文件 2_deploy_mitoken.js :

需要注意的是,在 migrations 目录下的部署文件名是有规则的,前缀以数字下划线命名 1_ 、2_ ...,这样每次 truffle 部署就可以根据数字去维护合约是否重复部署过(具体可以看 Migrations.sol 合约文件是如何实现的),如果之前部署过,下次在执行部署命令就不会重复执行了。

合约代码如下 :

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
//SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

contract DaMiToken is ERC20, Ownable {

    using SafeMath for uint256;

    //项目方地址
    address public projectAddress;
    //手续费千分比 txFeeRatio/1000
    uint256 public txFeeRatio;
    //销毁千分比 burnRatio/1000
    uint256 public burnRatio;

    /**
     * - 阅读 openzeppelin 中的 ERC20 源码,在标准 ERC20 基础上,开发以下功能的 ERC20 合约:
     * - 支持项目方增发的功能
     * - 支持销毁的功能
     * - 支持交易收取手续费至项目方配置的地址
     */
    constructor(
        string memory name_,
        string memory symbol_
    ) ERC20(name_, symbol_) {
    }

    //增发代币功能,空投
    function mint(address _account ,uint256 _amount) public onlyOwner {
        //新增多少的数量,进行相加
        _mint(_account, _amount);
    }

    //销毁代币
    function burn(uint256 _amount) public {
        require(balanceOf(msg.sender) >= _amount,"burn amount exceeds  balances");
        _burn(msg.sender, _amount);
    }

    //交易收取手续费至项目方配置的地址
    function transfer(address _to, uint256 _amount) public virtual override returns (bool) {
        //交易数量
        uint256 txFee = _amount.mul(txFeeRatio).div(1000);
        //燃烧掉的数量
        uint256 burnAmount = _amount.mul(burnRatio).div(1000);
        uint256 amount = _amount.sub(txFee).sub(burnAmount);
        //给项目方设置的地址转交易费
        _transfer(msg.sender,projectAddress ,txFee);
        _transfer(msg.sender, _to,amount);
        if (burnAmount > 0) {
            _burn(msg.sender, burnAmount);
        }
        return true;
    }

    //设置地址和交易费比例
    function setProjectAddress(address _projectWallet, uint256 _txFeeRatio,uint256 _burnRatio)
        external
        onlyOwner
    {
        projectAddress = _projectWallet;
        txFeeRatio = _txFeeRatio;
        burnRatio = _burnRatio;
    }
}

部署 js 文件代码:

const DaMiToken = artifacts.require("DaMiToken");

//通过合约部署设置构造函数的 name 和 symbol
module.exports = function (deployer) {
  deployer.deploy(DaMiToken,"DaMiToken","DaMi-Token");
};

三、修改 truffle 配置文件

点开 truffle-config.js 文件,可以看到默认生成了很多配置,非常友好:

在配置文件中,默认的测试网使用的是 ropsten 网络,但我这里习惯用 rinkeby 测试网,因为最早学习就是接触的 rinkeby ,所以 ETH 也是在这个网上比较多。

接下来,我们以 rinkeby 为例,按照默认的文件,仿照修改一下配置文件:

除了上图的配置外,这里提几点需要注意的,以及配置的含义:

第一步,引入依赖 HDWalletProvider

//安装依赖 @truffle/hdwallet-provider
 cnpm install @truffle/hdwallet-provider

这个是通过钱包和链上节点的交互配置,第一个需要传入你部署合约的钱包私钥 privatekey。我们可以通过环境变量对私钥设置,以防上传代码的时候泄露出去。

//环境变量私钥
var privateKey = process.env.privateKey;
export privateKey="你的钱包私钥"

第二步,需要传入你 infura 的节点 id。 infura 是一个区块链节点的服务商,其实 metamask 背后的默认 rpc 网络节点,也是它家提供的。 官网: https://infura.io/

自行注册,然后进入后台,点击右上角的 create project :

把这个 url 和 project id 复制到我们的配置文件中:

切忌,这里的 url 不要用 https 的,因为 rinkeby 会不定时出现以下错误:
Error: PollingBlockTracker - encountered an error while attempting to update latest

github给的解决方案:https://github.com/trufflesuite/truffle/issues/3357

同理,我们这里的 infuraid 也可以采用和私钥的配置形式,进行 export 到系统的环境变量中去。这里不重复赘述了。

到此,完整的配置文件:


const HDWalletProvider = require('@truffle/hdwallet-provider');
//环境变量私钥
var privateKey = process.env.privateKey;
//环境变量 infuraid
var infuraId = process.env.infuraId;

module.exports = {

  networks: {
    rinkeby: {
      // 钱包的节点提供服务
      provider: () => new HDWalletProvider(privateKey, "wss://rinkeby.infura.io/ws/v3/" + infuraId),
      gas: 10000000,    //部署接受的最大消耗 gas 
      gasPrice: 15000000000, //gas的价格
      network_id: 4,   //rinkeby的网络id
      timeoutBlocks: 40000, //读取区块链的数据超时时间
    },
  },
};

除了以上测试网的配置,还可以把优化的配置打开,编译器的版本设置对,根据自己合约的语法设定,设置成下面的默认就好,enabled 从 false 改成 true:

这里的 runs ,值越大,编译出来的文件越大,部署的成本越高,但执行效率相对好一些。越小则相反。默认值即可。

完整的配置文件:


const HDWalletProvider = require('@truffle/hdwallet-provider');
//环境变量私钥
var privateKey = process.env.privateKey;
//环境变量 infuraid
var infuraId = process.env.infuraId;

module.exports = {

  networks: {
    rinkeby: {
      // 钱包的节点提供服务
      provider: () => new HDWalletProvider(privateKey, "wss://rinkeby.infura.io/ws/v3/" + infuraId),
      gas: 10000000,    //部署接受的最大消耗 gas 
      gasPrice: 15000000000, //gas的价格
      network_id: 4,   //rinkeby的网络id
      timeoutBlocks: 40000, //读取区块链的数据超时时间
    },
  },
  mocha: {
    // timeout: 100000
  },

  compilers: {
    solc: {
      version: "0.8.11",      // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      settings: {          // See the solidity docs for advice about optimization and evmVersion
        optimizer: {
          enabled: true,
          runs: 200        //值越大,编译出来的文件越大,部署的成本越高,但执行效率相对好一些。越小则相反。默认值即可
        },
        evmVersion: "london"
      }
    }
  },
};

好,到这里,我们就可以尝试部署合约了,过程中肯定会遇到问题,来逐一解决。

四、truffle 部署合约

回到命令行中,我们只需要输入以下命令,指定 truffle 部署网络,便可以开始部署:

truffle migrate --network rinkeby

可以看到,缺少依赖了,npm安装一下,然后继续安装:

这里就不一个个截图了,最终安装的依赖如下:

//涉及到 web3 的
npm install web3-provider-engine
npm install ethereumjs-abi
//这个是我自己引用的 openzeppelin 依赖 
npm install @openzeppelin/contracts

当依赖问题解决,再次执行 truffle migrate 后:

这里提示一下,如果你的部署合约名称没变,但是合约代码有变化,还执行 truffle migrate 后,不会对合约重新部署:

此时,如果需要重新部署,需要通过一下命令进行部署:

truffle migrate --network rinkeby --reset

成功:

我们通过 etherscan 查看下部署的合约:

合约成功部署成功了,但此时的代码是未开源的,点击 contract ,可以看到如下,都是 16 进制码:

五、truffle 开源的两种方式

1、solidity contract flattener 开源

安装好这个插件,我们可以选中已经部署的合约源文件,点击下面的生成文件:

它会自动帮你把合约的 import 文件自动压缩到一个 solidity 文件中:

然后我们可以复制右侧的所有文件,到 etherscan 上进行验证开源。

点击下图的验证并发布:

按照大家自己的合约进行配置

选择:

把刚才生成的多文件压缩成一个文件的源码粘贴

过来:

点击验证并发布:

以上便是用 vscode 插件的全部开源验证流程。别忘了,优化的点一定要和 truffle 配置文件中的对应,否则有可能验证不通过。

2、truffle-plugin-verify 开源

上面的流程很繁琐,每次还需要手动上传,而且最终生成的开源代码在 etherscan 上是以单文件显示的,阅读并不友好。

既然用了 truffle ,就有自动化的方式来进行验证开源,且生成的文件还是多文件的。接下来,展示!用到的插件官网 github:
https://github.com/rkalis/truffle-plugin-verify

先安装好这个插件 truffle-plugin-verify :

npm install truffle-plugin-verify@latest

安装完成后,继续修改我们的 truffle 配置文件:

这个插件的原理,是用了 etherscan 的 api 进行了自动化部署并且验证,所以加上如上图所示后,你需要知道 2 件事。

第一点,关于 proxy ,是否要配置,这里决定着你访问 etherscan.io 这个网站是否走代理,国内的网络,必然是需要配置的,不配置本地代理访问,最终会报错:
Failed to connect to Etherscan API at url https://api-rinkeby.etherscan.io/api

配置完代理后,由于源码依赖于 tunnel ,所以还需要补个依赖,要不还是访问不了:

npm install tunnel

第二点,关于 etherscan 的 api key 申请,可以自行去官网申请:
https://etherscan.io/myapikey

依然是通过环境变量的方式,把 key 导入。

最终 truffle 配置文件如下:


const HDWalletProvider = require('@truffle/hdwallet-provider');
//环境变量私钥
var privateKey = process.env.privateKey;
//环境变量 infuraid
var infuraId = process.env.infuraId;
//环境变量 etherscan 的 apikey 
var etherscanApiKey = process.env.etherscanApiKey;
module.exports = {

  networks: {
    rinkeby: {
      // 钱包的节点提供服务
      provider: () => new HDWalletProvider(privateKey, "wss://rinkeby.infura.io/ws/v3/" + infuraId),
      gas: 10000000,    //部署接受的最大消耗 gas 
      gasPrice: 15000000000, //gas的价格
      network_id: 4,   //rinkeby的网络id
      timeoutBlocks: 40000, //读取区块链的数据超时时间
    },
  },
  mocha: {
    // timeout: 100000
  },

  compilers: {
    solc: {
      version: "0.8.11",      // Fetch exact version from solc-bin (default: truffle's version)
      // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
      settings: {          // See the solidity docs for advice about optimization and evmVersion
        optimizer: {
          enabled: true,
          runs: 200        //值越大,编译出来的文件越大,部署的成本越高,但执行效率相对好一些。越小则相反。默认值即可
        },
        evmVersion: "london"
      }
    }
  },
  
  plugins: ['truffle-plugin-verify'],

  verify: {
    proxy: {
      host: '127.0.0.1',
      port: '41091'
    }
  },

  api_keys: {
    etherscan: etherscanApiKey
  }
};

以上,配置完成后,回到命令行,执行以下命令:

//DaMiToken是合约名称,--debug可以看到具体错误信息
truffle run verify  DaMiToken --network rinkeby --debug

执行完毕后,可以看到成功:

打开 etherscan 的地址:

可以成功看到,合约已经被整整齐齐的分成了多个文件,并且成功开源!

至此,教程完毕。

好,以上就是完整的分享了....希望大家可以有所收获,有问题也欢迎随时交流探讨!
💎 |币圈萌新|NFT学习中|成为科学家的路上|💎

我的Twitter:

Subscribe to dami.eth
Receive the latest updates directly to your inbox.
Mint this entry as an NFT to add it to your collection.
Verification
This entry has been permanently stored onchain and signed by its creator.