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

dami.eth
2022-10-15 15:44
发布于 Mirror

前言

最近一直在折腾 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:

@dami

0
粉丝
0
获赞
13
精选
相关文章
数据来源区块链,不构成投资建议!
网站只展示作者的精选文章
2022 Tagge. With ❤️ from Lambda