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