以太坊智能合约开发框架:Hardhat v2 核心功能从入门到基础教程
一、设置项目
Hardhat 项目是安装了 hardhat
包并包含 hardhat.config.js
文件的 Node.js 项目。
操作步骤:
①初始化 npm
npm init -y
②安装 Hardhat
npm install --save-dev hardhat
③创建 Hardhat 项目
npx hardhat init
-
如果选择 Create an empty hardhat.config.js ,Hardhat 会生成如下配置文件:
/** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.28", };
-
Hardhat 默认是支持
Ethers
,如果使用Viem
可选择 Create a TypeScript project(with Viem) -
如果选择 Create a JavaScript project 、 Create a TypeScript project 、 Create a TypeScript project(with Viem),向导会询问几个问题,随后创建目录、文件并安装依赖。其中最重要的依赖是 Hardhat Toolbox ,它集成了Hardhat 所需的所有核心插件。
这里选择 Create a JavaScript project
初始化后的项目结构如下:
contracts/
ignition/modules/
test/
hardhat.config.js
这是 Hardhat 项目的默认路径:
contracts/
:存放合约源码ignition/modules/
:存放处理合约部署的 Ignition 模块test/
:存放测试文件
如需修改路径,可查看路径配置文档
二、VS Code 插件
Hardhat for Visual Studio Code 是官方 VS Code扩展,为 VS Code 提供 Solidity 高级支持。
三、Hardhat 架构
Hardhat 是围绕 任务
和 插件
的概念设计的,Hardhat 的大部分功能来自插件。
1.任务
每次从命令行运行 Hardhat 命令时,都在运行一项任务。
查看项目中当前可用的任务
npx hardhat
Hardhat 内置的常用任务:
- npx hardhat compile:编译Solidity合约代码
- npx hardhat test:运行测试脚本
- npx hardhat run [path/to/script.js]:运行一个脚本
- npx hardhat clean:清除构建输出和缓存文件
- npx hardhat console:交互式控制台
- npx hardhat node:启动本地开发节点
2.插件
Hardhat 的大部分功能来自插件,可在 Plugins 列表中查看官方推荐。
使用插件的步骤如下:
①安装插件
npm install --save-dev @nomicfoundation/hardhat-toolbox
②在 hardhat.config.js 文件中引入插件
require("@nomicfoundation/hardhat-toolbox"); module.exports = { solidity: "0.8.28",
};
四、网络
1.Hardhat 内置网络
内置的 Hardhat Network 作为开发测试网络,可搭配 Hardhat Network Helpers 库控制网络状态,这样更灵活。
启动 HardHat 网络节点
npx hardhat node
2.其他网络
Hardhat 默认网络是 Hardhat Network,如需使用其他网络(如以太坊测试网、主网或其他节点软件),可在 hardhat.config.js 导出对象的 networks
配置中进行设置,这是 Hardhat 项目管理网络配置的方式。
①配置外部网络(例如本地Geth)
module.exports = {networks: {geth: {url: "http://127.0.0.1:8545",accounts: ['你的私钥1', '你的私钥2', ...] },},
}
②使用外部网络
通过 --network
命令行参数可快速切换网络
npx hardhat [任务] --network [网络名]
如果不加 --network [网络名] 则将默认网络作为任务网络
3.更改默认网络
如果要切换默认网络,可在 hardhat.config.js 导出对象的 defaultNetwork
配置中进行设置(前提得先定义好 networks )。
module.exports = {networks: {geth: {url: "http://127.0.0.1:8545",accounts: ['私钥1', '私钥2', ...] },},// defaultNetwork: "geth", // 默认网络切换成 geth,但开发测试还是使用 hardhat 网络比较好
}
五、编写合约
在contracts
目录下编写 Lock 合约
contracts/Lock.sol
// SPDX许可标识符: 未经许可
pragma solidity ^0.8.28;contract Lock {// 公开状态变量:解锁时间戳 & 合约所有者地址uint public unlockTime;address payable public owner;// 定义提款事件(提款金额、操作时间)event Withdrawal(uint amount, uint when);// 构造函数:接收解锁时间并验证有效性// 必须附带 ETH 存款(payable 修饰)constructor(uint _unlockTime) payable {// 检查解锁时间是否在未来require(block.timestamp < _unlockTime,"Unlock time should be in the future");unlockTime = _unlockTime;owner = payable(msg.sender); // 设置部署者为所有者}// 提款函数(仅限所有者调用)function withdraw() public {// 验证当前时间是否已到解锁时间require(block.timestamp >= unlockTime, "You can't withdraw yet");// 验证调用者是否为所有者require(msg.sender == owner, "You aren't the owner");// 触发提款事件(合约余额、当前时间)emit Withdrawal(address(this).balance, block.timestamp);// 向所有者转账全部余额owner.transfer(address(this).balance);}
}
六、编译合约
1.执行编译任务
使用 Hardhat 内置的 compile
任务来编译合约
npx hardhat compile
这将会把
contracts/
目录下的所有合约进行编译,编译后会自动生成 artifacts 目录,并自动将编译相关的信息放在artifacts/
目录下。
后期如果仅修改了一个文件,那么只会重新编译该文件以及受其影响的其他文件。
这是因为 Hardhat 有缓存机制。Hardhat会将每个智能合约的编译结果缓存起来,以便在后续的编译过程中重复使用。
这意味着如果您没有对合约进行任何更改,Hardhat将直接从缓存中读取编译结果,而不需要重新编译整个合约。如果对合约部分修改,那么会差量编译。这样可以极大地减少编译时间,特别是在项目中存在多个合约的情况下。
但如果想强制进行编译,可以使用 --force
参数,或者运行 npx hardhat clean
来清除缓存并删除编译产物。
# 清除编译缓存
npx hardhat clean
# 强制编译
npx hardhat compile --force
2.配置编译器
如果需要自定义 Solidity 编译器选项,可以通过 hardhat.config.js 中的 solidity
字段来实现。使用该字段最简单的方式是通过简写形式来设置编译器版本。Hardhat 会自动下载所设置的 solc
(solidity 编译器) 版本。
module.exports = {solidity: "0.8.28",
};
如果不指定版本则以 Hardhat 指定的默认版本
建议自定义版本,因为如果后期随着 Solidity 新版本发布, Hardhat 官方可能会修改默认的编译器版本 ,从而导致项目出现意外行为或编译错误
注意:合约的版本与配置的编译器版本不兼容,Hardhat 将会抛出错误。
3.更多编译器相关配置
module.exports = {solidity: {version: "0.8.28",settings: {optimizer: {enabled: true,runs: 1000,},evmVersion: 'london'},},
};
settings
的结构与可以传递给编译器的输入 JSON 中的 settings
条目相同。一些常用的设置如下:
- optimizer:一个包含
enabled
和runs
键的对象。默认值为{ enabled: false, runs: 200 }
。 - evmVersion:一个字符串,用于控制目标 EVM 版本。例如:
istanbul
、berlin
或london
。默认值由solc
管理。
七、测试合约
本指南将介绍在 Hardhat 中测试合约的推荐方法。该方法借助 ethers
库连接到 Hardhat 网络,使用 Mocha
和 Chai
进行测试。同时,还会用到自定义的 Chai 匹配器以及 Hardhat 网络辅助工具,从而更轻松地编写简洁的测试代码。这些工具包均属于 Hardhat Toolbox 插件的一部分。
虽然这是推荐的测试设置方式,但 Hardhat 具有很高的灵活性:可以对该方法进行自定义,也可以采用其他工具开辟全新的测试路径。
1.测试工具
1.1. mocha
Mocha 是一个能够运行在 Node.js
和 浏览器
中的多功能 JavaScript 测试框架,它让异步测试变得 简单 和 有趣。Mocha 顺序运行测试,并给出灵活而精确的报告,同时能够将未捕获的异常映射到准确的测试用例上。
1️⃣describe
是一个 Mocha 函数,可组织测试
- 参数: 接收测试组织名称和回调函数。回调必须定义该部分的测试。这个回调不能是异步函数。
- 全Mocha函数在全局范围内可用、组织好测试可以让调试变得更容易。
describe("学习mocha测试", function () {// 里面装测试函数it
})
2️⃣it
是另一个 Mocha 函数。可定义每个测试单元
- 参数:接收单元测试名称和回调函数。
- 如果回调函数是异步的,Mocha 将自动 “await” 它
describe("Mocha测试", function () {it("测试单元1", async function () {// 具体的测试})it("测试单元2", async function () {// 具体的测试})
})
3️⃣beforeEach
是 Mocha 中 describe 函数的一个钩子。可在执行 describe 函数中 it 函数 之前执行
describe("Mocha测试", function () {beforeEach(async function () {// 在执行 it 函数前做些什么});it("测试单元1", async function () {// 具体的测试})it("测试单元2", async function () {// 具体的测试})
})
1.2 chai
chai 是一个可以在 node
和 浏览器
环境运行的BDD
/TDD
断言库,可以和任何 JavaScript 测试框架结合。在 Hardhat 中对其进行了加强,使得 chain 更符合合约的测试。学习Chai断言库
2.测试变量
测试内容:
- 部署
Lock
合约 - 断言
unlockTime()
返回的解锁时间与在构造函数中传入的时间一致
测试准备:
-
查看 contracts/Lock.sol 合约代码,了解逻辑
-
在 test 目录下新建 myLock.js 测试文件,编写测试代码
测试步骤:
①导入所需的测试工具:
- 从
chai
中导入expect
函数用于编写断言 - 导入 Hardhat 运行时环境(
hre
) - 与 Hardhat 网络交互的网络辅助工具
const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");
②使用 describe
和 it
函数,它们是Mocha
的全局函数,用于描述和分组测试
const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {it("应设置正确的解锁时间", async function () {// 测试代码位于 it 函数的回调参数内。});
});
③编写部署合约的逻辑
首先,设置要锁定的金额(以 wei 为单位)和解锁时间。对于解锁时间,使用 time.latest
这个网络辅助工具,它会返回最后一个已挖出区块的时间戳。然后,部署合约:调用ethers.deployContract
,传入要部署的合约名称和包含解锁时间的构造函数参数数组。再传入一个包含交易参数的对象,这是可选的,但通过设置其 value
字段来发送一些 ETH。
const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {it("应设置正确的解锁时间", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一个可以提取资金的锁定合约// 未来一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});});
});
④测试合约变量
检查合约中 unlockTime()
getter 方法返回的值是否与部署时使用的值相匹配。
由于调用合约上的所有函数都是异步的,必须使用 await
关键字来获取其值;否则,将比较一个 Promise 和一个数字,这肯定会失败。
const { expect } = require("chai");
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {it("应设置正确的解锁时间", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一个可以提取资金的锁定合约// 未来一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});// 断言该值是正确的expect(await lock.unlockTime()).to.equal(unlockTime);});
});
3.测试回滚函数
在之前的测试中,检查了一个 getter 函数是否返回了正确的值。这是一个只读函数,可以免费调用且没有任何风险。
然而,其他函数可能会修改合约的状态,且再修改状态之前会有一些前置检查,比如 Lock
合约中的 withdraw
函数。这意味着希望在调用这个函数之前满足一些前置条件。如果查看该函数的前几行,会看到有几个 require
检查用于此目的:
contracts/Lock.sol
function withdraw() public {require(block.timestamp >= unlockTime, "You can't withdraw yet");require(msg.sender == owner, "You aren't the owner");
}
第一条语句检查是否已达到解锁时间,第二条语句检查调用合约的地址是否为合约所有者。
为第一个前置条件编写测试:
it("如果调用过快,应返回正确的错误", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一个可以提取资金的锁定合约// 未来一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,})await expect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet");
});
在之前的测试中,使用了 .to.equal
,这是 Chai
的一部分,用于比较两个值。在这里,使用 .to.be.revertedWith
,它用于断言交易将回滚,并且回滚的原因字符串等于给定的字符串。 .to.be.revertedWith
匹配器并非 Chai
本身的一部分,而是由 Hardhat Chai Matchers
插件添加的
注意,在之前的测试中写的是 expect(await ...)
,但现在是 await expect(...)
。在第一种情况下,是以同步方式比较两个值;内部的 await
只是为了等待获取值。在第二种情况下,整个断言是异步的,因为它必须等待交易被挖出。这意味着 expect
调用返回一个 Promise,必须对其使用 await
。
4.操纵网络时间
部署的 Lock
合约的解锁时间为一年。如果想编写一个测试来检查解锁时间过后会发生什么,显然不能真的等上一年。可以使用更短的解锁时间,比如 5 秒,但这不是一个很现实的值,而且在测试中等待 5 秒仍然很长。
解决办法是模拟时间的流逝。这可以通过 time.increaseTo
网络辅助工具来实现,它会挖出一个带有给定时间戳的新区块:
it("应将资金转移给所有者", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一个可以提取资金的锁定合约// 未来一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,})// 生成指定时间戳的最新区块await time.increaseTo(unlockTime);// 如果交易回滚,这里会抛出错误await lock.withdraw();
});
如前所述,调用 lock.withdraw()
会返回一个 Promise。如果交易失败,该 Promise 将被拒绝。使用 await
在这种情况下会抛出错误,所以如果交易回滚,测试将失败。
5.使用不同的账户
withdraw
函数进行的第二个检查是调用该函数的地址是否为合约所有者。默认情况下,部署和函数调用是使用第一个配置的账户进行的。如果想检查只有所有者才能调用某个函数,就需要使用不同的账户,并验证调用会失败。
ethers.getSigners()
会返回一个包含所有配置账户的数组。可以使用合约的 .connect
方法,用不同的账户调用函数,并检查交易是否回滚:
it("如果从其他帐户调用,应返回正确的错误", async function () {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;// 部署一个可以提取资金的锁定合约// 未来一年const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,})// 获取账户列表,并解构const [owner, otherAccount] = await hre.ethers.getSigners();// 增加链上的时间以通过第一个检查await time.increaseTo(unlockTime);// 使用lock.connect()从另一个账户发送交易await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith("You aren't the owner");});
这里再次调用一个函数,并断言它会以正确的原因字符串回滚。不同之处在于用 .connect(anotherAccount)
从不同的地址调用该方法。
6.使用固定装置(Fixtures)
到目前为止,在每个测试中都部署了 Lock
合约。这意味着在每个测试开始时,都必须获取合约工厂,然后部署合约。对于单个合约来说,这可能没问题,但如果设置更复杂,每个测试开始时都会有几行代码只是为了设置所需的状态,而且大多数时候这些代码都是相同的。
在典型的 Mocha
测试中,这种代码重复问题可以通过beforeEach
钩子来处理。
const hre = require("hardhat");
const { time } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {let lock: any;let unlockTime: number;let lockedAmount = 1_000_000_000;beforeEach(async function () {const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});});it("some test", async function () {// 使用已部署的合约});
});
然而,这种方法有两个问题:
- 如果需要部署多个合约,测试会变慢,因为每个测试作为设置的一部分都要发送多个交易。
- 在
beforeEach
钩子和测试之间像这样共享变量既不美观又容易出错。
Hardhat 网络辅助工具中的 loadFixture
助手解决了这两个问题。这个助手接收一个固定装置(fixture),即一个将链设置到所需状态的函数。第一次调用 loadFixture
时,会执行该固定装置函数。但第二次调用时, loadFixture
不会再次执行固定装置函数,而是将网络状态重置到固定装置函数执行后的状态。这样更快,并且会撤销前一个测试所做的任何状态更改。
const { expect } = require("chai");
const hre = require("hardhat");
const { time, loadFixture } = require("@nomicfoundation/hardhat-toolbox/network-helpers");describe("Lock", function () {// 定义固定装置函数async function deployOneYearLockFixture() {const lockedAmount = 1_000_000_000;const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;const lock = await hre.ethers.deployContract("Lock", [unlockTime], {value: lockedAmount,});return { lock, unlockTime, lockedAmount };}it("应设置正确的解锁时间", async function () {// 使用固定装置(第一次使用,初始化执行一次deployOneYearLockFixture函数,并记录当前区块), 并获取合约实例和解锁时间const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);// 断言值是正确的expect(await lock.unlockTime()).to.equal(unlockTime);});it("如果调用过快,应返回正确的错误", async function () {// 使用固定装置(第二次使用,回滚区块到初始化deployOneYearLockFixture函数所记录的区块), 并获取合约实例和解锁时间const { lock } = await loadFixture(deployOneYearLockFixture);await expect(lock.withdraw()).to.be.revertedWith("You can't withdraw yet");});it("应将资金转移给所有者", async function () {// 使用固定装置(第二次使用,回滚区块到初始化deployOneYearLockFixture函数所记录的区块), 并获取合约实例和解锁时间const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);// 生成指定时间戳的最新区块await time.increaseTo(unlockTime);// 如果交易回滚,这里会抛出错误await lock.withdraw();});it("如果从其他帐户调用,应返回正确的错误", async function () {// 使用固定装置(第二次使用,回滚区块到初始化deployOneYearLockFixture函数所记录的区块), 并获取合约实例和解锁时间const { lock, unlockTime } = await loadFixture(deployOneYearLockFixture);// 获取账户列表,并解构const [owner, otherAccount] = await hre.ethers.getSigners();// 增加链上的时间以通过第一个检查await time.increaseTo(unlockTime);// 使用lock.connect()从另一个账户发送交易await expect(lock.connect(otherAccount).withdraw()).to.be.revertedWith("You aren't the owner");});
});
固定装置函数可以返回任何想要的值,loadFixture
助手会返回该值。建议像这里一样返回一个对象,这样就可以提取出该测试中关心的值。
7.使用调试
Hardhat Network 允许通过从 Solidity 代码调用来打印日志记录消息和合约变量
使用步骤:
①在合约中导入 hardhat 的 console.sol
import "hardhat/console.sol";
②在合约中使用console.log()
// SPDX许可标识符: 未经许可
pragma solidity ^0.8.28;// 导入 console.sol 库
import "hardhat/console.sol";contract Lock {// 公开状态变量:解锁时间戳 & 合约所有者地址uint public unlockTime;address payable public owner;// 定义提款事件(提款金额、操作时间)event Withdrawal(uint amount, uint when);// 构造函数:接收解锁时间并验证有效性// 必须附带 ETH 存款(payable 修饰)constructor(uint _unlockTime) payable {// 检查解锁时间是否在未来require(block.timestamp < _unlockTime,"Unlock time should be in the future");unlockTime = _unlockTime;owner = payable(msg.sender); // 设置部署者为所有者}// 提款函数(仅限所有者调用)function withdraw() public {// 调试console.log("unlockTime %d,currentTimestamp %d", unlockTime, block.timestamp);// 验证当前时间是否已到解锁时间require(block.timestamp >= unlockTime, "You can't withdraw yet");// 验证调用者是否为所有者require(msg.sender == owner, "You aren't the owner");// 触发提款事件(合约余额、当前时间)emit Withdrawal(address(this).balance, block.timestamp);// 向所有者转账全部余额owner.transfer(address(this).balance);}
}
8.执行测试任务
使用 Hardhat 内置的 test
任务来执行测试脚本
npx hardhat test
这将会执行
test/
目录下所有测试脚本
很明显第一个单元测试时间花费最长 522ms ,这是因为初始化执行 fixture函数。
9.其他测试
9.1 测量测试覆盖率
Hardhat Toolbox 包含 solidity-coverage
插件,用于测量项目的测试覆盖率。只需运行 coverage
任务,就会得到一份报告:
npx hardhat coverage
9.2 使用 gas 报告器
Hardhat Toolbox 还包含 hardhat-gas-reporter
插件,根据测试执行情况获取使用了多少 gas 的指标。当执行test
任务且设置了 REPORT_GAS
环境变量时,会运行 gas 报告器:
REPORT_GAS=true npx hardhat test
对于 Windows 用户,在 PowerShell 会话中设置环境变量的命令是$env:REPORT_GAS="true"
:
$env:REPORT_GAS="true"; npx hardhat test
八、部署合约
1.编写部署模块
若要部署合约,可使用声明式部署系统 Hardhat Ignition。
新建负责部署 Lock
合约的 Ignition 模块 LockModule
位于 ignition/modules 目录下
ignition/modules/Lock.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules"); const JAN_1ST_2030 = 1893456000; // 默认值为 2030 年 1 月 1 日,时间戳`1893456000`
const ONE_GWEI = 1_000_000_000n; // 默认值为 1 Gwei,即`1_000_000_000n`module.exports = buildModule("LockModule", (m) => { const unlockTime = m.getParameter("unlockTime", JAN_1ST_2030); const lockedAmount = m.getParameter("lockedAmount", ONE_GWEI); const lock = m.contract("Lock", [unlockTime], { value: lockedAmount, }); return { lock };
});
2.执行部署任务
npx hardhat ignition deploy ./ignition/modules/你的部署模块文件名.js --network <网络名>
若未指定网络,Hardhat Ignition 将部署到 hardhat.config.js 配置的默认网络。
1️⃣部署到 Hardhat 网络
npx hardhat ignition deploy ./ignition/modules/Lock.js
2️⃣指定网络部署
npx hardhat ignition deploy ./ignition/modules/Lock.js --network <网络名>
九、配置变量
1.为什么需要配置变量?
Hardhat 项目中使用配置变量,是为存储用户特定值、保护敏感数据(如 API 密钥、私钥等),便于项目共享协作,提高代码可维护性与灵活性,以及适应不同环境。
注意:配置变量以明文形式存储在磁盘上,请勿用于存储不希望以未加密文件形式保存的数据。可通过 npx hardhat vars path
查看存储文件位置。
2.配置变量和环境变量的区别
dotenv 环境变量:若使用 dotenv
,可能导致意外上传 .env
文件导致敏感数据泄露。
vars 配置变量:Hardhat 项目可以将配置变量用于用户特定的值或不应包含在代码存储库中的数据。这些变量是通过作用域中的任务设置的,可以使用对象在配置中检索。
3.在命令行中操作配置变量
1️⃣设置配置变量(为配置变量赋值,不存在则创建)
npx hardhat vars set 配置变量名
2️⃣获取配置变量
npx hardhat vars get 配置变量名
3️⃣查询计算机上存储的所有的配置变量
npx hardhat vars list
4️⃣删除配置变量语法
npx hardhat vars delete 配置变量名
4.在文件中使用配置变量
①导入 vars 实例
const { vars } = require("hardhat/config");
②使用配置变量
-
直接使用
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("环境变量名");
-
使用配置变量并设置默认值(变量不存在时使用)
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("环境变量名", "默认值");
-
先检查再使用
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.has("环境变量名") ? [vars.get("环境变量名")] : '备选值';
5.使用配置变量存储私钥
①先设置私钥的配置变量
npx hardhat vars set PRIVATE_KEY
可以在 hardhat.config.js 文件中检索存储的配置变量。
require("@nomicfoundation/hardhat-toolbox");
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("PRIVATE_KEY");/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {solidity: {version: "0.8.28",settings: {optimizer: {enabled: true,runs: 1000,},evmVersion: 'london'},},networks: {geth: {url: "http://127.0.0.1:8545",accounts: [PRIVATE_KEY] },},// defaultNetwork: "geth", // 默认网络切换成 geth
};
十、验证合约
验证合约是指公开合约的源代码以及所用的编译器设置,这样任何人都能编译该代码,并将生成的字节码与链上部署的字节码进行对比。在像以太坊这样的开放平台上,这一操作极其重要。
本指南将介绍如何在 Etherscan 浏览器中完成此操作。
提示:如果想验证非 Hardhat Ignition 部署的合约,或者想在 Sourcify 而非 Etherscan 上验证合约,可以使用 hardhat-verify
插件。
1.从 Etherscan 获取 API 密钥
操作步骤:
①首先,你需要从 Etherscan 获取一个 API 密钥。
具体操作如下:访问 Etherscan 网站,登录账号(若没有则需注册),打开 “API Keys” 标签页,点击 “Add” 按钮,为你创建的 API 密钥命名(例如 “Hardhat”),之后你会在列表中看到新创建的密钥。
②将 Etherscan 的 API 密钥存在配置变量中
npx hardhat vars set ETHERSCAN_API_KEY
③在 Hardhat 的 hardhat.config.js 中 添加 etherscan
配置,并将 API 密钥添加到这里
const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY");module.exports = {// ...其他配置...etherscan: {apiKey: ETHERSCAN_API_KEY,},
};
2.在 Sepolia 测试网部署并验证合约
将使用 Sepolia 测试网来部署和验证合约,因此需要在 Hardhat 配置文件中添加该网络。这里使用 Infura 连接到网络,如果愿意,也可以使用其他 JSON - RPC URL,如 Alchemy。
操作步骤:
①访问 https://infura.io 注册账号,在其控制台创建一个新的 API 密钥
②将 INFURA 的 API 密钥存储到配置变量中
npx hardhat vars set INFURA_API_KEY
③在 Hardhat 的 hardhat.config.js 的 networks
中添加 sepolia
网络配置,并将 API 密钥添加到这里
const INFURA_API_KEY = vars.get("INFURA_API_KEY");export default {// ...其他配置...networks: {sepolia: {url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,accounts: [PRIVATE_KEY],},},
};
要在 Sepolia 上部署合约,需要向进行部署的地址发送一些 Sepolia 以太币。可以从水龙头获取测试网以太币,水龙头是一种免费分发测试以太币的服务。以下是一些 Sepolia 水龙头:
- Alchemy Sepolia Faucet
- QuickNode Sepolia Faucet
- Ethereum Ecosystem Sepolia Faucet
现在可以部署合约了,但在此之前,要让合约的源代码具有唯一性。
打开的合约文件,添加一条包含独特信息的注释,比如你的 GitHub 用户名。请记住,在这里添加的任何内容都会和代码的其他部分一样,在 Etherscan 上公开可见:
contracts/Lock.sol
// Author: @你的名称
contract Lock {
将利用在 “部署合约” 指南中创建的 Ignition 模块 Lock
来进行部署。使用 Hardhat Ignition 和新添加的 Sepolia 网络运行部署命令:
npx hardhat ignition deploy ignition/modules/Lock.js --network sepolia --deployment-id sepolia-deployment
提示:--deployment-id
标志是可选的,但它允许你为部署指定一个自定义名称。这样在后续操作中,比如验证合约时,引用起来会更方便。
最后,要验证已部署的合约,你可以运行 ignition verify
任务并传入部署 ID:
npx hardhat ignition verify sepolia-deployment
或者,你可以使用 --verify
标志调用 deploy
任务,将部署和验证合并为一步:
npx hardhat ignition deploy ignition/modules/Lock.js --network sepolia --verify
提示:如果你收到错误信息,提示地址没有字节码,这可能意味着 Etherscan 尚未对合约进行索引。这种情况下,等待一分钟后再试。
当 ignition verify
任务成功执行后,将看到一个指向你合约公开验证代码的链接。
十一、自定义任务
Hardhat 本质上是一个任务运行器,借助它能够让开发工作流程实现自动化。它自带了像 compile
和 test
这类内置任务,同时也可以自行添加自定义任务。
1️⃣编写自定义任务
编写不带参数的基本任务,该任务会打印出可用账户的列表,同时探究其工作原理。
hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
const { vars } = require("hardhat/config");const PRIVATE_KEY = vars.get("PRIVATE_KEY");
const ETHERSCAN_API_KEY = vars.get("ETHERSCAN_API_KEY");
const INFURA_API_KEY = vars.get("INFURA_API_KEY");// 自定义任务
task("accounts", "Prints the list of accounts", async (taskArgs, hre) => {const accounts = await hre.ethers.getSigners();for (const account of accounts) {console.log(account.address);}
});/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {solidity: {version: "0.8.28",settings: {optimizer: {enabled: true,runs: 1000,},evmVersion: 'london'},},networks: {geth: {url: "http://127.0.0.1:8545",accounts: [PRIVATE_KEY] },sepolia: {url: `https://sepolia.infura.io/v3/${INFURA_API_KEY}`,accounts: [PRIVATE_KEY],},},// defaultNetwork: "geth", // 默认网络切换成 gethetherscan: {apiKey: ETHERSCAN_API_KEY,},
};
task
函数来定义新任务。
-
它的第一个参数是任务名称,也就是在命令行中用来运行任务的名称
-
第二个参数是任务描述,当你使用
npx hardhat help
时会显示该描述 -
第三个参数是一个异步函数,在你运行任务时会执行这个函数。它接收两个参数:
-
一个包含任务参数的对象。目前还没有定义任何参数。
-
Hardhat 运行时环境(HRE),它包含了 Hardhat 及其插件的所有功能。在任务执行期间,能发现它的所有属性被注入到全局命名空间中。
-
在这个函数里,可以自由地实现任何功能。在这个例子中,使用 ethers.getSigners()
来获取所有已配置的账户,并打印出每个账户的地址。
可以为任务添加参数,Hardhat 会帮你处理参数的解析和验证。还可以覆盖现有的任务,这样就能改变 Hardhat 不同部分的工作方式。
2️⃣执行自定义任务
npx hardhat accounts
十二、Hardhat 控制台
Hardhat 内置了一个交互式 JavaScript 控制台。通过运行以下命令即可使用:
$ npx hardhat console
Welcome to Node.js v12.10.0.
Type ".help" for more information.
>
打开控制台前会先调用 compile
任务,若需跳过可使用 --no-compile
参数
npx hardhat console --no-compile
1.执行环境
控制台的执行环境与任务、脚本和测试完全一致:配置已处理完毕,Hardhat 运行时环境(HRE)已初始化并注入全局作用域。
-
config
:查看 Hardhat 配置对象> config { solidity: { compilers: [ [Object] ], overrides: {} }, defaultNetwork: 'hardhat', ... } >
-
ethers
:若按入门指南操作或安装了@nomicfoundation/hardhat-ethers> ethers { Signer: [Function: Signer] { isSigner: [Function] }, ... provider: EthersProviderWrapper { ... }, getSigners: [Function: getSigners], getContractAt: [Function: bound getContractAt] AsyncFunction } >
所有注入到 HRE 中的内容都会自动在全局作用域中可用。如需显式引用 HRE,也可通过require
导入:
> const hre = require("hardhat")
> hre.ethers
{ /* 与上述ethers对象一致 */ }
2.历史记录功能
控制台支持大多数交互式终端的历史记录功能(包括跨会话记录),可通过向上箭头键查看历史命令。本质上,Hardhat 控制台是 Node.js 控制台的实例,因此 Node.js 的所有功能均可在此使用。
3.异步操作与顶级 await
与以太坊网络(及智能合约)的交互均为异步操作,因此大多数 API 和库通过 JavaScript 的 Promise
返回值。
为简化操作,Hardhat 控制台支持顶级 await语句(例如直接使用await
调用异步函数):
> console.log(await ethers.getSigners())
[ Signer { address: '0xf39F...', provider: Provider }, Signer { address: '0x7099...', provider: Provider }, ...
]
-
config
:查看 Hardhat 配置对象> config { solidity: { compilers: [ [Object] ], overrides: {} }, defaultNetwork: 'hardhat', ... } >
-
ethers
:若按入门指南操作或安装了@nomicfoundation/hardhat-ethers> ethers { Signer: [Function: Signer] { isSigner: [Function] }, ... provider: EthersProviderWrapper { ... }, getSigners: [Function: getSigners], getContractAt: [Function: bound getContractAt] AsyncFunction } >
所有注入到 HRE 中的内容都会自动在全局作用域中可用。如需显式引用 HRE,也可通过require
导入:
> const hre = require("hardhat")
> hre.ethers
{ /* 与上述ethers对象一致 */ }
2.历史记录功能
控制台支持大多数交互式终端的历史记录功能(包括跨会话记录),可通过向上箭头键查看历史命令。本质上,Hardhat 控制台是 Node.js 控制台的实例,因此 Node.js 的所有功能均可在此使用。
3.异步操作与顶级 await
与以太坊网络(及智能合约)的交互均为异步操作,因此大多数 API 和库通过 JavaScript 的 Promise
返回值。
为简化操作,Hardhat 控制台支持顶级 await语句(例如直接使用await
调用异步函数):
> console.log(await ethers.getSigners())
[ Signer { address: '0xf39F...', provider: Provider }, Signer { address: '0x7099...', provider: Provider }, ...
]
相关文章:
以太坊智能合约开发框架:Hardhat v2 核心功能从入门到基础教程
一、设置项目 Hardhat 项目是安装了 hardhat 包并包含 hardhat.config.js 文件的 Node.js 项目。 操作步骤: ①初始化 npm npm init -y②安装 Hardhat npm install --save-dev hardhat③创建 Hardhat 项目 npx hardhat init如果选择 Create an empty hardhat.…...
【现代深度学习技术】现代循环神经网络06:编码器-解码器架构
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈PyTorch深度学习 ⌋ ⌋ ⌋ 深度学习 (DL, Deep Learning) 特指基于深层神经网络模型和方法的机器学习。它是在统计机器学习、人工神经网络等算法模型基础上,结合当代大数据和大算力的发展而发展出来的。深度学习最重…...
Jasper and Stella: distillation of SOTA embedding models
摘要 Dense检索是许多深度学习应用系统中(例如常见问题 (FAQ) 和检索增强生成 (RAG))关键组成部分。在此过程中,Embedding模型将原始文本转换为向量。然而,目前在文本Embedding基准…...
程序的本质—API接口
API 是什么 官方定义:API 英文全称 Application Programming Interface,翻译过来为程序之间的接口。也是程序与外部世界的桥梁,实现了服务拆分与解藕的核心机制 大白话:按照规则提供输入,它帮你得到对应的输出 API可…...
Python函数完全指南:从零基础到灵活运用
Python函数是组织代码、实现代码复用的基本单元,也是Python编程中最重要的概念之一。本文将全面介绍Python函数的知识体系,帮助初学者系统掌握函数的使用方法。 一、函数基础概念 1. 什么是函数? 函数是一段可重复使用的代码块,…...
第十章.XML
文章目录 1.XMl简介2.解析XML技术2.1DOM解析XML 2.2DOM4j3.json 1.XMl简介 EXtensible Markup Language ,可扩充标记语言 特点: XML与操作系统,编程语言的开发平台无关实现不同系统之间的数据交换 作用: 数据交互配置应用程序和网站 XML标签 xml文档由一系列标签元素组成<…...
5个情感丰富GPT-4o图像提示词(不是吉卜力风格)
一场新的创意运动正在迅速兴起——人们不仅使用ChatGPT进行写作,还用它来构思富有想象力、情感丰富的视觉概念。 这一趋势正在改变我们讲故事、建立品牌和探索创意的方式。从异想天开的海报世界到基于物品的故事叙述,各行业的创作者正在将ChatGPT与视觉生成工具结合使用,赋…...
华为OD机试真题——求最多可以派出多少支队伍(2025A卷:100分)Java/python/JavaScript/C/C++/GO最佳实现
2025 A卷 100分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录…...
《七年一剑》速读笔记
文章目录 书籍信息概览知己知彼市场的本质认识自我了解他人人剑合一 技术分析精要K线分型均线节奏形态画线成交量周期级别指标之王——MACD波动理论 管窥基本面A股周期论实战角度看财报 构建交易系统打开交易之门交易基础之买卖依据风险控制与仓位管理系统评估及情绪管理 实战秘…...
VMware-centOS7安装redis分布式集群
1.部署redis包 1.1 在usr/local文件夹里创建文件夹 mkdir software 1.2 进入文件夹 cd /usr/local/software/ 下载redis wget http://download.redis.io/releases/redis-6.2.6.tar.gz解压 tar zxvf redis-6.2.6.tar.gz重命名文件夹 mv redis-6.2.6 redis安装gcc编译器 yum i…...
Kubernetes(k8s)学习笔记(六)--KubeSphere前置环境安装
1、安装 helm(master 节点执行) Helm 是 Kubernetes 的包管理器。包管理器类似于我们在 Ubuntu 中使用的apt、Centos 中使用的 yum 或者 Python 中的 pip 一样,能快速查找、下载和安装软件包。Helm由客户端组件 helm 和服务端组件 Tiller 组…...
黑马点评day01(基于Redis)
1.7 Redis代替session的业务流程 1.7.1、设计key的结构 首先我们要思考一下利用redis来存储数据,那么到底使用哪种结构呢?由于存入的数据比较简单,我们可以考虑使用String,或者是使用哈希,如下图,如果使用…...
14.Excel:排序和筛选
一 位置 两个位置。 二 排序:如何使用 1.常规使用 补充:不弹出排序提醒排序。 选中要排序列中的任意一个单元格,然后排序。 2.根据要求进行排序 1.根据姓名笔画进行降序排序 要勾选上数据包含标题,默认是勾选了。 2.根据运营部、…...
力扣-字符串-468 检查ip
思路 考察字符串的使用,还有对所有边界条件的检查 spilt(“\.”),toCharArray,Integer.parseInt() 代码 class Solution {boolean checkIpv4Segment(String str){if(str.length() 0 || str.length() > 4) retur…...
C++名称空间
名称空间 名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非它引用了常量) 名称空间是开放的,你可以在…...
Redis 过期与淘汰机制全解析
概述 Redis 作为一种高性能的内存数据库,提供了数据过期和淘汰策略以管理存储的数据。本文将详细探讨 Redis 中数据失效的基本原理、实现方式,并结合源码进行分析,帮助读者更深入地理解和优化 Redis 的使用。 数据过期机制 过期键的存储方…...
PMP-第四章 项目整合管理(一)
项目整合管理 项目整合管理包括对项目管理过程组内的各种过程和项目管理活动而进行识别、定义、组合、统一与协调的各种过程和活动项目整合管理必须由项目经理负责。其他知识领域可以由相关领域专家管理,但整合的责任不能被授权或转移项目与项目管理本质上具有整合…...
VSCode搭建STM32开发调试环境
闲言碎语: 好久没更,在忙着科研→校招→写毕业论文。 临近毕业,总结自己的大学生活:C\C、Java、Python、深度学习,学的乱七八糟。 秋招找了个嵌入式工作(涉及AI应用),大致确定了以后…...
【数据结构】稀疏矩阵的快速转置
稀疏矩阵的快速转置 如图给出一个稀疏矩阵,要求表示出它的转置矩阵 由这个矩阵我们能轻松得到它的三元组顺序表 6行(x坐标)7列(y坐标)8个元素121213931-3361443245218611564-7 接下来我们同样把转置后的矩阵的三元组…...
【Godot】使用 Shader 实现可调节的精确切角效果
今天我们要实现的是一个四角精确切割Shader,可以在UI元素或Sprite的四个角分别切割出不同大小的三角形区域。 文章目录 什么是Godot Shader?数学原理详解左上角切割右上角切割右下角切割左下角切割四角切割Shader完整代码使用方法在Godot编辑器中设置通过代码控制进阶技巧1. …...
在CentOS环境中安装MySQL数据库保姆级教程
一.确认当前系统版本 1.1登录系统,切换至root账户 如图所示: 1.2:在终端中执行如下命令查看系统版本 cat /etc/redhat-release 二.添加 MySQL Yum 源 2.1访问MySQL开发者专区 https://dev.mysql.com/downloads/repo/yum/ TIPS: 1.发布包命…...
分布式系统中的 ActiveMQ:异步解耦与流量削峰(二)
四、流量削峰 (一)流量削峰原理深入解析 在当今互联网应用中,高并发场景屡见不鲜 。例如,电商平台的促销活动、在线票务系统的抢票时刻以及社交平台的热点事件爆发期等,都会在短时间内迎来大量用户请求。这些瞬间涌入…...
JAVA设计模式——(十)抽象工厂模式(Abstract Factory Pattern)
JAVA设计模式——(十)抽象工厂模式(Abstract Factory Pattern) 介绍理解实现工厂接口工厂实现类应用类应用类实现测试改造工厂类 应用 介绍 抽象工厂模式在工厂模式的基础上,适配的对象变为一组相关的对象,…...
STM32的定时器
定时器的介绍 介绍:STM32F103C8T6微控制器内部集成了多种类型的定时器,这些定时器在嵌入式系统中扮演着重要角色,用于计时、延时、事件触发以及PWM波形生成、脉冲捕获等应用。 *几种定时器(STM32F103系列)࿱…...
ubuntu-PyQt5安装+PyCharm配置QtDesigner + QtUIC
个人环境 ubuntu22.04 pycharm 2024.3 python 3.10 1)先使用apt命令在线安装 1)sudo apt install pyqt5* 2)sudo apt install qttools5-dev-tools2)Pycharm配置Pycharm External Tool 在设置—工具——外部工具中 配置QtDesigner Name :QtDesigne…...
测试基础笔记第十九天
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、接口的概念二、接口的类型三、接口测试1.概念2.原理:3.特点:4.实现方式:5.什么是自动化接口测试? 二、HTTP协议1.HTTP协议简介2.URL格式…...
Ubuntu 系统上广受好评的浏览器推荐
日常使用与开发者首选 Firefox 特点:开源、隐私保护强大,支持丰富扩展(如开发者工具、广告拦截),默认预装且跨平台兼容368。 适用场景:日常浏览、开发者调试(支持实时 CSS/JS 编辑)、…...
第 13 届蓝桥杯 C++ 青少组省赛中 / 高级组真题解析
一、选择题 第 1 题 题目:下列关于类中声明的变量描述正确的是 ( )。 选项: A. 只属于该类 B. 属于全局变量 C. 任何情况下都可被该类所有实例共享 D. 属于该类,某些情况下也可被该类不同实例所共享 答案:D 解析&…...
Win10下安装Linux-Ubuntu24.04双系统
0 引言 Ubuntu 24.04 LTS(代号“Noble Numbat”)是 Canonical 于 2024 年 4 月 25 日发布的第 10 个长期支持版本,专注于性能优化、企业安全和开发者体验提升 Windows 10 是微软于 2015 年 7 月发布的跨平台操作系统,融合了传统桌…...
express 怎么搭建 WebSocket 服务器
一:使用 express-ws var express require(express); var app express(); var expressWs require(express-ws)(app);app.use(function (req, res, next) {console.log(middleware);req.testing testing;return next(); });app.get(/, function(req, res, next){…...
模型部署——cuda编程入门
CUDA中的线程与线程束 kernel是在device上线程中并行执行的函数,核函数用__global__符号声明,在调用时需要用<<<grid_size, block_size>>>来指定kernel要执行的线程数量。在CUDA中,每一个线程都要执行核函数,并…...
llfc项目TCP服务器笔记
ChatServer 一个TCP服务器必然会有连接的接收,维持,收发数据等逻辑。那我们就要基于asio完成这个服务的搭建。主服务是这个样子的 #include "LogicSystem.h"#include <csignal>#include <thread>#include <mutex>#include "AsioIOServiceP…...
NPP库中libnppi模块介绍
1. libnppi 模块简介 libnppi 是 NPP 库中专门用于 图像处理 的模块,提供高度优化的 GPU 加速函数,支持: 图像滤波(卷积、形态学操作) 几何变换(旋转、缩放、透视变换) 颜色空间转换…...
从头训练小模型: 3 传统RLHF与DPO的区别
这个步骤我其实是忽略了。如果我的目标是建立一个安全领域的模型,我个人理解这步骤并不太必要。关于人类偏好对齐:在前面的训练步骤中,模型已经具备了基本的对话能力。 此时模型还不知道什么是好的回答,什么是不好的回答。我们希…...
Python-Django系列—视图
一、通用显示视图 以下两个基于类的通用视图旨在显示数据。在许多项目中,它们通常是最常用的视图。 1、DetailView class django.views.generic.detail.DetailView 当该视图执行时,self.object 将包含该视图正在操作的对象。 祖先(MRO&a…...
el-input Vue 3 focus聚焦
https://andi.cn/page/622173.html...
动态规划(5)路径问题--剑指offer -珠宝的最大值
题目: 现有一个记作二维矩阵 frame 的珠宝架,其中 frame[i][j] 为该位置珠宝的价值。拿取珠宝的规则为: 只能从架子的左上角开始拿珠宝每次可以移动到右侧或下侧的相邻位置到达珠宝架子的右下角时,停止拿取 注意࿱…...
ZArchiver正版:高效文件管理,完美解压体验
在使用安卓设备的过程中,文件管理和压缩文件的处理是许多用户常见的需求。无论是解压下载的文件、管理手机存储中的文件,还是进行日常的文件操作,一款功能强大且操作简便的文件管理工具都能极大地提升用户体验。今天,我们要介绍的…...
Netlink在SONiC中的应用
Netlink在SONiC中的应用 Netlink介绍 Netlink 是 Linux 内核态程序与用户空间程序之间进行通信的机制之一,原本是用于传递网络协议栈中的各种控制消息。它采用和套接字(socket)编程接口相同的形式,常用于配置内核网络子系统&…...
ReentrantLock实现公平锁和非公平锁
在 Java 里,公平锁和非公平锁是多线程编程中用于同步的两种锁机制,它们的主要差异在于获取锁的顺序规则。下面是对二者的详细介绍: 公平锁 公平锁遵循 “先来先服务” 原则,也就是线程获取锁的顺序和请求锁的顺序一致。先请求锁…...
【C++】 —— 笔试刷题day_25
一、笨小猴 题目解析 这道题,给定一个字符str,让我们找到这个字符串中出现次数最多字母的出现次数maxn和出现次数最少字母的出现次数minn; 然后判断maxn - minn是否是一个质数,如果是就输出Lucky Word和maxn - minn;如…...
terraform resource创建了5台阿里云ecs,如要使用terraform删除其中一台主机,如何删除?
在 Terraform 中删除阿里云 5 台 ECS 实例中的某一台,具体操作取决于你创建资源时使用的 多实例管理方式(count 或 for_each)。以下是详细解决方案: 方法一:使用 for_each(推荐) 如果创建时使…...
Office 三大组件Excel、Word、Access 里 VBA 区别对比
以下是Excel、Word和Access在VBA中的主要区别对比及详细说明: 核心对象模型 Excel Workbook(工作簿)→ Worksheet(工作表)→ Range(单元格区域) 核心围绕单元格数据处理,如 Cells(1,1).Value = "数据" Word Document(文档)→ Range(文本范围)→ Paragrap…...
Linux 进程基础(二):操作系统
目录 一、什么是操作系统:用户和电脑之间的「翻译官」🌐 OS 的层状结构🧩 案例解析:双击鼠标的「跨层之旅」 二、操作系统的必要性探究:缺乏操作系统的环境面临的挑战剖析🔑 OS 的「管理者」属性࿱…...
Java高并发处理核心技术详解:从理论到实战
高并发处理能力是衡量系统性能的重要指标。Java作为企业级开发的主力语言,提供了丰富的并发编程工具和框架。 一、Java并发基础 1.1 Java内存模型(JMM) 主内存与工作内存:每个线程拥有独立的工作内存,通过JMM协议与主…...
单细胞测序数据分析试验设计赏析(二)
单细胞测序数据分析试验设计赏析(二) 这次的单细胞测序数据分析的试验设计是单细胞测序分析机器学习(with SHAP分析),也是常见的试验设计之一,重点是可以用于筛选鉴定基因调控网络,也可以是构建…...
Docker 服务搭建
💢欢迎来到张翊尘的开源技术站 💥开源如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥 文章目录 Docker 服务搭建在 Ubuntu 上安装 Docker更新软件…...
4电池_基于开关电容的均衡
基于开关电容的均衡系统(Switched-Capacitor Equalization System) 开关电容均衡(Switched-Capacitor Equalization, SCE)是一种广泛应用于 电池组(如锂电池、超级电容组) 的主动均衡技术,通过电…...
Matlab/Simulink - BLDC直流无刷电机仿真基础教程(七) - 波形解析专题P2
Matlab/Simulink - BLDC直流无刷电机仿真基础教程(七) - 波形解析专题P2 前言一、缺相与相线错接解析二、电源电压波动三、电机感量及磁链变化四、负载突变及堵转五、换相时机不当及换相错误参考链接 前言 本系列文章分享如何使用Matlab的Simulink功能来…...
如何从GitHub上调研优秀的开源项目,并魔改应用于工作中?
在 Go 语言学习中,我们经常会去学习一些优秀的开源项目。但是学完之后,发现很快就忘记了或者学习效果并不好。学习一个开源项目最好的方式就是围绕这个开源项目进行实战。例如,直接魔改这个开源项目并应用于工作中。本文来介绍下如何调用&…...