hardhat开发框架4
上一篇: hardhat开发框架3
在上一篇中,我们了解到了
- 学习solidity必须要学习开发框架。hardhat就如同python里的FastAPI或者Flask
- Cancun是以太坊重要升级的代号
- Moche是一个JavaScript测试框架
- ether.js提供了一个简单的api和以太坊进行交互
小测试
让我为你提供三个全新的问题,帮助你测试一下 Solidity 的知识:
- 在 Solidity 中,为什么建议遵循“检查-效果-交互”(Checks-Effects-Interactions)模式,特别是在涉及外部调用时?
- Solidity 中
delegatecall与普通的call调用有什么区别?如何利用delegatecall实现合约的升级? - 什么是 Solidity 中的“重入攻击”(Reentrancy Attack),以及如何通过
nonReentrant修饰符来防范这种攻击?
这些问题应该没有重复,希望能帮助你更好地检验知识。如果有任何不清楚的地方,欢迎随时提问!
答: 1. 遵循“检查-效果-交互”可以防止重入攻击
2. delegatecall是代理合约。合约升级有两种代理合约和逻辑合约。但我只停留在理论上,还没有实际使用过delegatecall进行合约的升级
3. 重入攻击指的是攻击者反复调用合约的某个函数。例如让用户存款归零。没有使用过nonReentrant修饰符,我猜测是要在合约调用前先进行函数的检查,遵循检查-效果-交互的模范。
你的回答已经非常接近正确了!下面是我针对每个问题的详细反馈和补充说明:
检查-效果-交互模式:
- 你的理解:完全正确!遵循“检查-效果-交互”(Checks-Effects-Interactions)模式的关键是先进行条件检查、更新合约状态,然后再进行外部调用(如转账)。这种顺序确保了外部调用之前合约的状态已经固定,从而有效避免了重入攻击。攻击者无法在合约外部调用之间反复进入合约。
- 补充:这种模式不仅防止重入攻击,还能减少由于状态未更新导致的错误。
delegatecall与合约升级:- 你的理解:你提到
delegatecall是代理合约的基础,概念上是对的!delegatecall是一种特殊的调用方式,允许一个合约在另一个合约的上下文中执行代码(即使用调用方的存储)。合约升级通常是通过代理合约来实现的,代理合约通过delegatecall来执行逻辑合约的代码,这样就可以在不改变存储的情况下更新逻辑。 - 补充:实际应用中,使用代理模式时,逻辑合约的地址可以灵活更新。你可以通过代理合约实现逻辑合约的升级,而不需要迁移存储数据。Proxy Pattern 就是利用
delegatecall来进行合约升级的典型实现方式。
- 你的理解:你提到
重入攻击与
nonReentrant修饰符:- 你的理解:正确!重入攻击通常发生在外部调用的合约在资金转账或状态更新之前被攻击者恶意重新调用的情况下。而
nonReentrant修饰符通过锁定合约状态,防止函数在执行过程中被再次调用,这样就可以有效防止重入攻击。 - 补充:
nonReentrant是一种常见的防重入攻击的方式,它通过设置一个锁定标志,确保函数在执行时无法被再次进入。你可以在函数开始时使用该修饰符,来确保没有其他合约能在此函数执行期间重新调用同一个函数。
- 你的理解:正确!重入攻击通常发生在外部调用的合约在资金转账或状态更新之前被攻击者恶意重新调用的情况下。而
总结来看,你的理解已经非常到位了!只要在实际项目中多加练习,逐渐应用这些概念到代码中,实际经验会进一步加深对这些技巧的掌握。如果你有任何问题,或者需要更深入的示例,随时可以告诉我!
gas优化策略
在 Solidity 中,Gas 优化是智能合约开发中的一个重要方面,旨在减少合约的执行成本和提高效率。以下是一些常见的 Gas 优化策略:
减少存储操作:
- 存储操作(写入区块链状态)是最昂贵的。尽量减少对状态变量的修改,尤其是在 存储 中。
- 使用
memory代替 **storage**:如果变量的生命周期很短,尽量使用内存中的变量,而非状态变量。memory中的变量比storage更便宜。
优化数据类型:
- 避免使用过大的数据类型。例如,使用
uint8或uint16等较小的类型,避免无谓的uint256,因为较小的数据类型能节省存储空间。 - 在合约中使用
uint8等小整数类型,合理选择数据类型来节省存储空间。
- 避免使用过大的数据类型。例如,使用
避免循环(loops):
- 避免在合约中使用大量的循环,特别是循环内有状态变化的情况。每次状态变化都会消耗Gas。
- 如果必须使用循环,请确保其循环次数是固定的,并且尽可能少。
合并条件检查:
- 尽量将
require或if语句中的条件合并,以减少多次检查。例如,避免多个require检查,可以将多个条件合并为一个,减少交易的执行开销。
- 尽量将
合理使用
emit事件:- 事件的触发是免费的,但事件的日志存储会消耗Gas,因此确保事件的触发是必要的,避免不必要的事件触发。
利用
unchecked语句:- Solidity 0.8.0 及以后版本引入了
unchecked语句,允许跳过溢出检查。这在没有溢出风险时(如计数器)能节省Gas。
- Solidity 0.8.0 及以后版本引入了
Gas 优化合约设计:
- 使用合约代理(Proxy)模式:通过代理合约分离存储和业务逻辑,这样在更新逻辑合约时可以减少不必要的状态迁移操作 。
这些优化策略将有助于提高合约的执行效率,减少Gas费用,使得部署和交互更为经济高效。如果你有具体的合约或代码,想进一步优化,我可以帮助你分析如何实施这些优化策略。
问题一 每次创建新项目是不是都要安装hardhat?
是的,每次创建新的 Hardhat 项目都需要重新安装 Hardhat。
为什么需要每次安装?
- 项目隔离:每个项目都有自己独立的
node_modules目录和依赖版本 - 版本控制:不同项目可能需要不同版本的 Hardhat 或插件
- 依赖管理:
package.json文件记录项目特定的依赖关系
正确的项目创建流程:
1 | |
项目间共享配置的方法:
虽然 Hardhat 需要每个项目单独安装,但你可以通过以下方式提高效率:
方法1:创建项目模板
1 | |
方法2:使用自定义脚本
创建一个初始化脚本 init-hardhat.sh:
1 | |
方法3:全局安装(不推荐)
1 | |
实际项目示例:
1 | |
总结:
- ✅ 每个新项目都需要单独安装 Hardhat
- ✅ 依赖安装在项目的
node_modules目录中 - ✅
package.json记录项目特定的依赖 - ❌ 不要依赖全局安装的 Hardhat
- ✅ 可以创建模板来加速新项目初始化
这样确保了项目的独立性和可维护性,避免了版本冲突问题。
问题二 Hardhat Ignition 是什么?
Ignition 是 Hardhat 的新一代部署管理系统,用于:
管理复杂的部署流程
处理合约依赖关系
支持升级部署
提供更好的部署状态管理
在hardhat部署合约
现在你一步一步带我如何在hardhat部署该合约
在 Hardhat 中部署智能合约是一个系统性的过程,下面我为你梳理了一份清晰的步骤指南。整个过程主要步骤可见下表:
| 步骤 | 主要任务 | 关键命令/文件 |
|---|---|---|
| 1. 环境准备 | 安装Node.js、创建项目目录、初始化项目 | node --version, npm init -y |
| 2. 安装配置Hardhat | 安装Hardhat及相关插件、创建配置文件 | npm install --save-dev hardhat, npx hardhat, hardhat.config.js |
| 3. 准备合约 | 编写Solidity合约、编译 | contracts/目录, npx hardhat compile |
| 4. 配置网络与环境变量 | 设置部署网络、添加私钥和RPC URL | .env文件, hardhat.config.js中的networks配置 |
| 5. 编写部署脚本 | 使用Ethers.js编写部署逻辑 | scripts/目录, deploy.js |
| 6. 执行部署 | 运行部署脚本 | npx hardhat run scripts/deploy.js --network <network_name> |
接下来,我们详细看看每一步的操作。


npx hardhat --init
npm install --save-dev @nomicfoundation/hardhat-toolbox-viem @nomicfoundation/hardhat-ignition viem
npx hardhat node 用于启动一个本地的以太坊测试网络。
npx hardhat ignition deploy ignition/modules/Counter.ts --network localhost 这个命令是使用 Hardhat Ignition 来部署合约,这是 Hardhat 的一个新的部署系统。
去中心化投票系统
🔧 1. 环境准备与项目初始化
首先,确保你的系统环境符合要求,并初始化一个新的项目。
- 安装 Node.js:确保你安装了 Node.js(建议使用 LTS 版本)。你可以在终端中输入
node --version来检查。 - 创建项目目录并初始化:
1
2
3
4
5mkdir my-voting-project
cd my-voting-project
npm init -y
# 初始化 Hardhat 项目
npx hardhat --init

⚙️ 2. 安装并配置 Hardhat
在你的项目目录中,安装Hardhat并创建基础配置。
- 安装 Hardhat:
1
npm install --save-dev hardhat - 创建 Hardhat 项目:运行以下命令,然后选择 “Create a TypeScript project” (或者”Create a JavaScript project”)来生成一个示例项目结构。这会在当前目录创建
1
npx hardhatcontracts/、scripts/、test/等文件夹和一个hardhat.config.js文件。 - 安装有用插件:一些插件能让开发更便捷,建议安装:
1
npm install --save-dev @nomicfoundation/hardhat-toolbox
📄 3. 准备你的投票合约
现在,将你编写好的Solidity投票合约文件(例如 DecentralizedVoting.sol)放入项目中的 contracts/ 目录。
- 编译合约:使用以下命令编译合约,确保没有错误:编译成功后,你会看到
1
npx hardhat compileCompilation finished successfully的提示,并且会生成一个artifacts文件夹,其中包含了合约的ABI和字节码。

🌐 4. 配置网络与环境变量
为了将合约部署到特定的区块链网络(例如本地测试网或公共测试网),你需要配置 hardhat.config.js 文件。
- 修改配置文件:打开
hardhat.config.js,添加网络配置。以下是一个配置示例,展示了如何设置对Sepolia测试网和Hardhat本地网络的支持:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config(); // 加载.env文件中的环境变量
module.exports = {
solidity: "0.8.19", // 确保与你的合约版本一致
networks: {
// 配置 Sepolia 测试网
sepolia: {
url: `https://sepolia.infura.io/v3/${process.env.INFURA_API_KEY}`, // 你的Infura或其他节点服务商URL
accounts: [process.env.SEPOLIA_PRIVATE_KEY], // 你的钱包私钥
},
// 配置 Hardhat 本地网络 (用于测试)
localhost: {
url: "http://127.0.0.1:8545",
// accounts: Hardhat 在运行时会自动提供一组测试账户
},
},
}; - 设置环境变量:为了安全,敏感信息如私钥和API密钥不应直接写在代码里。创建一个
.env文件来管理它们:1
2INFURA_API_KEY="你的Infura项目ID"
SEPOLIA_PRIVATE_KEY="你的Sepolia测试网钱包私钥"- 获取API密钥:你可以从 Infura 或 Alchemy 等服务获取免费的RPC节点URL。
- 获取测试币:若部署到Sepolia等测试网,你需要一些测试网ETH。可以从 Chainlink Faucet 等水龙头获取。
📜 5. 编写部署脚本
Hardhat使用脚本来部署合约。
- 创建部署脚本:在
scripts/目录下创建一个文件,例如deploy_voting.js。 - 编写部署代码:以下是一个基本的部署脚本示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// scripts/deploy_voting.js
async function main() {
// 获取部署者账户
const [deployer] = await ethers.getSigners();
console.log("使用账户部署合约:", deployer.address);
// 获取合约工厂
const DecentralizedVoting = await ethers.getContractFactory("DecentralizedVoting");
// 部署合约,如果需要可传递构造函数参数
const votingContract = await DecentralizedVoting.deploy();
await votingContract.waitForDeployment(); // 等待合约被部署上链
// 获取合约地址
const contractAddress = await votingContract.getAddress();
console.log("投票合约部署地址:", contractAddress);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
🚀 6. 执行部署
- 部署到本地网络:部署成功后,终端会输出合约的部署地址,请务必记录下来。
1
2
3
4# 首先在一个终端启动本地节点
npx hardhat node
# 然后在另一个终端运行部署命令
npx hardhat run scripts/deploy_voting.js --network localhost
🛠️ 进阶步骤(可选)
- 验证合约:在部署到公共测试网或主网后,为了便于在区块浏览器(如Etherscan)上查看和交互,你可以验证合约源代码。这通常需要用到
hardhat-etherscan插件。 - 编写测试:Hardhat提供了强大的测试环境。你可以在
test/目录下编写测试文件,使用npx hardhat test命令来运行测试,确保合约逻辑正确。
💎 总结
遵循以上步骤,你应该能顺利地在Hardhat环境中部署你的去中心化投票合约。核心步骤就是初始化项目、配置网络、编写脚本并执行部署。如果在过程中遇到问题,可以多查阅Hardhat官方文档和相关教程。
希望这份指南能帮助你顺利完成合约部署!如果你在部署过程中遇到其他具体问题,比如在配置网络或编写脚本时出了错,欢迎随时追问。