# Truffle部署Solidity合约教程(etherscan多文件自动开源) **Published by:** [dami.eth](https://paragraph.com/@yidakoumi/) **Published on:** 2022-06-12 **URL:** https://paragraph.com/@yidakoumi/truffle-solidity-etherscan ## Content 前言最近一直在折腾 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 latestgithub给的解决方案: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 ,所以还需要补个依赖,要不还是访问不了:第二点,关于 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 ## Publication Information - [dami.eth](https://paragraph.com/@yidakoumi/): Publication homepage - [All Posts](https://paragraph.com/@yidakoumi/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@yidakoumi): Subscribe to updates - [Twitter](https://twitter.com/jacksu15ice): Follow on Twitter