news 2026/4/15 13:25:38

从零搭建以太坊私链:手把手教你配置Devnet开发环境(Geth+Hardhat版)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零搭建以太坊私链:手把手教你配置Devnet开发环境(Geth+Hardhat版)

从零搭建以太坊私链:手把手教你配置Devnet开发环境(Geth+Hardhat版)

当你在开发DeFi协议或NFT项目时,是否遇到过这样的困境:每次测试智能合约都要消耗真实的测试网代币,调试过程还要忍受漫长的区块确认时间?搭建本地Devnet开发环境就是解决这些痛点的最佳方案。本文将带你用最流行的以太坊客户端Geth和开发框架Hardhat,从创世区块开始构建一个完全自主控制的以太坊私有链。

1. 环境准备与工具安装

在开始之前,我们需要准备以下开发工具和环境。不同于公共测试网,私有Devnet给了你完全的控制权——你可以自定义Gas价格、区块时间、甚至是共识机制。这对于需要频繁测试合约交互的开发者来说,能节省大量等待时间。

1.1 基础软件安装

首先确保你的系统已经安装:

  • Node.js (v16.x或更高版本)
  • npm/yarn
  • Git

然后安装核心工具:

npm install -g ganache-cli hardhat

对于Geth客户端的安装,各平台略有不同:

Mac用户

brew tap ethereum/ethereum brew install ethereum

Linux用户

sudo add-apt-repository -y ppa:ethereum/ethereum sudo apt-get update sudo apt-get install ethereum

Windows用户: 建议使用Chocolatey包管理器:

choco install geth

1.2 初始化Hardhat项目

创建一个新的项目目录并初始化Hardhat:

mkdir eth-devnet && cd eth-devnet npx hardhat init

选择"Create a JavaScript project"选项,这将为你生成基本的项目结构。Hardhat的优势在于它内置了本地网络节点,但我们将配置它连接到我们自己的Geth私有链,以获得更真实的开发体验。

2. 配置创世区块

创世区块是区块链的起点,它定义了网络的基本参数。我们将创建一个名为genesis.json的文件来定制我们的私有链。

2.1 创世文件配置

{ "config": { "chainId": 1337, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "berlinBlock": 0, "londonBlock": 0 }, "alloc": { "0xYourAccountAddress": { "balance": "1000000000000000000000" } }, "coinbase": "0x0000000000000000000000000000000000000000", "difficulty": "0x20000", "extraData": "", "gasLimit": "0x2fefd8", "nonce": "0x0000000000000042", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x00" }

关键参数说明:

  • chainId: 设置为1337,这是本地开发常用的链ID
  • difficulty: 调低挖矿难度,加速区块生成
  • gasLimit: 设置较高的gas上限,方便测试复杂合约
  • alloc: 预分配测试ETH到你的开发账户

2.2 初始化Geth节点

使用创世文件初始化你的私有链:

geth init --datadir=./chaindata genesis.json

这将在chaindata目录下创建区块链的初始状态。--datadir参数指定了区块链数据的存储位置,保持项目结构整洁很重要。

3. 启动私有链节点

现在我们可以启动我们的私有链节点了。Geth提供了多种配置选项来优化开发体验。

3.1 基本启动命令

geth --datadir ./chaindata \ --networkid 1337 \ --http \ --http.addr 0.0.0.0 \ --http.port 8545 \ --http.api "eth,net,web3,personal,miner" \ --allow-insecure-unlock \ --mine \ --miner.threads 1 \ --miner.etherbase "0xYourAccountAddress"

参数解析:

  • --networkid: 必须与创世文件中的chainId一致
  • --http: 启用HTTP-RPC服务器
  • --mine: 启用挖矿,自动处理交易
  • --miner.threads: 控制挖矿CPU使用率
  • --allow-insecure-unlock: 允许账户解锁(仅限开发环境)

3.2 优化开发体验

为了更方便的调试,建议添加以下参数:

--verbosity 3 \ --metrics \ --metrics.expensive \ --pprof \ --pprof.addr 0.0.0.0 \ --pprof.port 6060

这些选项启用了性能监控和pprof分析工具,当你的DApp变得复杂时,这些工具对性能调优非常有帮助。

4. 配置Hardhat连接私有链

现在我们需要配置Hardhat来使用我们的Geth私有链,而不是它内置的本地网络。

4.1 修改hardhat.config.js

require("@nomicfoundation/hardhat-toolbox"); module.exports = { solidity: "0.8.19", networks: { devnet: { url: "http://127.0.0.1:8545", accounts: [ "0x你的私钥" // 仅用于开发环境! ] } } };

安全提示:永远不要在版本控制中提交私钥。考虑使用环境变量或.env文件来管理敏感信息。

4.2 账户管理最佳实践

在开发环境中,我们可以使用Geth的控制台创建测试账户:

geth attach http://localhost:8545

在Geth控制台中执行:

personal.newAccount("你的密码") personal.unlockAccount(eth.accounts[0], "你的密码", 0)

更安全的方式是使用Hardhat的账户插件:

npm install @nomicfoundation/hardhat-network-helpers

然后在测试脚本中:

const helpers = require("@nomicfoundation/hardhat-network-helpers"); const account = await helpers.createRandomAccount();

5. 智能合约开发与部署流程

现在环境已经搭建完成,让我们走一遍完整的智能合约开发部署流程。

5.1 创建示例合约

contracts目录下创建SimpleStorage.sol

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleStorage { uint256 private storedData; event ValueChanged(uint256 newValue); function set(uint256 x) public { storedData = x; emit ValueChanged(x); } function get() public view returns (uint256) { return storedData; } }

5.2 编写部署脚本

scripts目录下创建deploy.js

const hre = require("hardhat"); async function main() { const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage"); const simpleStorage = await SimpleStorage.deploy(); await simpleStorage.deployed(); console.log(`SimpleStorage deployed to: ${simpleStorage.address}`); } main().catch((error) => { console.error(error); process.exitCode = 1; });

5.3 部署合约

运行部署命令:

npx hardhat run scripts/deploy.js --network devnet

你应该会看到类似输出:

SimpleStorage deployed to: 0x5FbDB2315678afecb367f032d93F642f64180aa3

5.4 与合约交互

创建一个测试脚本scripts/interact.js

const hre = require("hardhat"); async function main() { const contractAddress = "0x5FbDB2315678afecb367f032d93F642f64180aa3"; const SimpleStorage = await hre.ethers.getContractAt("SimpleStorage", contractAddress); // 设置值 const setTx = await SimpleStorage.set(42); await setTx.wait(); // 获取值 const value = await SimpleStorage.get(); console.log("Stored value:", value.toString()); } main().catch((error) => { console.error(error); process.exitCode = 1; });

6. 高级配置与优化技巧

基本的Devnet已经搭建完成,但要让开发环境更高效,还需要一些进阶配置。

6.1 自定义Gas价格

hardhat.config.js中为devnet网络添加:

gasPrice: 20000000000, // 20 Gwei gas: 8000000

或者在Geth启动时设置:

--miner.gasprice 20000000000 \ --miner.gaslimit 8000000

6.2 自动挖矿配置

默认情况下,Geth需要手动启动挖矿。我们可以配置自动挖矿:

--mine \ --miner.threads 1 \ --miner.etherbase "0xYourAccountAddress" \ --miner.gastarget 8000000 \ --miner.gasprice 20000000000

或者使用JavaScript API在Geth控制台中:

miner.start(1)

6.3 快照与状态恢复

开发过程中经常需要重置链状态。Geth支持快照功能:

创建快照:

geth snapshot dump --datadir ./chaindata snapshot.json

恢复快照:

geth snapshot restore --datadir ./chaindata snapshot.json

6.4 跨项目共享配置

如果你有多个项目使用相同的Devnet配置,可以创建一个共享的Hardhat配置:

// shared-hardhat-config.js module.exports = { networks: { devnet: { url: "http://localhost:8545", chainId: 1337, gasPrice: 20000000000 } } }; // 项目中的hardhat.config.js const sharedConfig = require("../shared-hardhat-config"); module.exports = { ...sharedConfig, solidity: "0.8.19" };

7. 常见问题排查

即使按照指南操作,开发过程中仍可能遇到各种问题。以下是几个常见问题及其解决方案。

7.1 连接问题排查

症状:Hardhat无法连接到Geth节点

检查步骤:

  1. 确认Geth正在运行并监听正确端口
    lsof -i :8545
  2. 检查Geth日志是否有错误
  3. 尝试直接通过curl测试连接:
    curl -X POST --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' http://localhost:8545

7.2 交易卡住不处理

解决方案

  1. 检查是否启用了挖矿:
    eth.mining
    如果返回false,启动挖矿:
    miner.start(1)
  2. 检查账户是否解锁:
    eth.accounts personal.listAccounts
  3. 增加Gas价格:
    miner.setGasPrice(20000000000)

7.3 合约部署失败

常见原因

  1. Solidity版本不匹配
  2. Gas不足
  3. 构造函数参数错误

排查方法

  1. 检查Hardhat编译日志
  2. 增加部署脚本中的Gas限制:
    const contract = await Contract.deploy({ gasLimit: 8000000 });
  3. 使用--verbose标志运行部署脚本

7.4 性能优化技巧

当你的Devnet开始变慢时:

  1. 定期清理旧的链数据:
    geth removedb --datadir ./chaindata geth init --datadir=./chaindata genesis.json
  2. 增加缓存大小:
    --cache 4096
  3. 禁用不需要的API:
    --http.api "eth,net,web3"

8. 集成测试与持续开发

一个完善的Devnet环境应该支持自动化测试和持续集成流程。

8.1 编写自动化测试

test目录下创建SimpleStorage.test.js

const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("SimpleStorage", function () { it("Should store and retrieve a value", async function () { const SimpleStorage = await ethers.getContractFactory("SimpleStorage"); const simpleStorage = await SimpleStorage.deploy(); await simpleStorage.deployed(); // 设置值 await simpleStorage.set(42); // 验证值 expect(await simpleStorage.get()).to.equal(42); }); });

运行测试:

npx hardhat test --network devnet

8.2 配置CI/CD流程

创建一个基本的GitHub Actions工作流.github/workflows/test.yml

name: Smart Contract Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '16' - name: Install dependencies run: npm install - name: Start Geth Devnet run: | geth init --datadir=./chaindata genesis.json geth --datadir ./chaindata \ --networkid 1337 \ --http \ --http.addr 0.0.0.0 \ --http.port 8545 \ --http.api "eth,net,web3,personal" \ --allow-insecure-unlock \ --mine \ --miner.threads 1 & - name: Run tests run: npx hardhat test --network devnet

8.3 模拟主网环境

为了更接近主网环境,可以调整以下参数:

  1. 修改genesis.json
    • 增加difficulty
    • 调整gasLimit到接近主网的值
  2. 使用主网区块浏览器API:
    // hardhat.config.js networks: { devnet: { // ... forking: { url: "https://mainnet.infura.io/v3/YOUR_PROJECT_ID", blockNumber: 15815693 // 特定区块高度 } } }
  3. 设置合理的Gas价格:
    await hre.network.provider.send("hardhat_setNextBlockBaseFeePerGas", ["0x" + (15e9).toString(16)])

9. 扩展Devnet功能

基础Devnet搭建完成后,你可以根据需要扩展更多功能来模拟真实的生产环境。

9.1 添加多个节点

创建真正的P2P网络而不是单节点:

  1. 为每个节点创建独立的数据目录:
    mkdir -p chaindata/node{1..3}
  2. 分别初始化每个节点:
    geth init --datadir=./chaindata/node1 genesis.json
  3. 获取节点enode信息:
    geth --datadir ./chaindata/node1 console --exec "admin.nodeInfo.enode"
  4. 启动节点时指定静态节点:
    geth --datadir ./chaindata/node2 \ --port 30304 \ --bootnodes "enode://..."

9.2 监控与数据分析

配置Geth的监控接口:

--metrics \ --metrics.expensive \ --pprof \ --pprof.addr 0.0.0.0 \ --pprof.port 6060

然后可以使用Prometheus和Grafana来可视化监控数据:

  1. 配置Prometheus收集指标:
    # prometheus.yml scrape_configs: - job_name: 'geth' static_configs: - targets: ['localhost:6060']
  2. 导入Geth的Grafana仪表板模板

9.3 集成IPFS

对于NFT项目,通常需要IPFS支持:

  1. 安装IPFS:
    curl -O https://dist.ipfs.io/go-ipfs/v0.12.0/go-ipfs_v0.12.0_linux-amd64.tar.gz tar -xvzf go-ipfs_v0.12.0_linux-amd64.tar.gz cd go-ipfs ./install.sh
  2. 初始化并启动IPFS节点:
    ipfs init ipfs daemon
  3. 在Hardhat项目中使用IPFS:
    npm install @pinata/sdk
    然后创建上传脚本:
    const pinataSDK = require('@pinata/sdk'); const pinata = new pinataSDK('yourPinataApiKey', 'yourPinataSecretApiKey'); const fs = require('fs'); const readableStreamForFile = fs.createReadStream('./path/to/file.png'); pinata.pinFileToIPFS(readableStreamForFile).then((result) => { console.log(result.IpfsHash); });

9.4 实现跨链测试环境

虽然真正的跨链需要更复杂的设置,但你可以模拟多链环境:

  1. 创建不同chainId的多个Devnet:
    // genesis1.json "config": { "chainId": 1337 } // genesis2.json "config": { "chainId": 31337 }
  2. 使用Hardhat的forking功能模拟跨链调用:
    // hardhat.config.js networks: { devnet1: { url: "http://localhost:8545", chainId: 1337 }, devnet2: { url: "http://localhost:8546", chainId: 31337, forking: { url: "http://localhost:8545", blockNumber: 12345 } } }
  3. 编写跨链测试用例:
    describe("Cross-chain", function() { it("should bridge assets", async function() { const [owner] = await ethers.getSigners(); // 在链1上锁定资产 const chain1 = await ethers.getSigner("devnet1"); await chain1.sendTransaction({ to: bridgeAddress, value: ethers.utils.parseEther("1.0") }); // 在链2上验证并铸造 const chain2 = await ethers.getSigner("devnet2"); const balance = await chain2.getBalance(); expect(balance).to.equal(ethers.utils.parseEther("1.0")); }); });

10. 安全最佳实践

虽然Devnet是开发环境,但养成良好的安全习惯很重要,特别是当你的代码最终要部署到主网时。

10.1 账户安全管理

  1. 永远不要在代码中硬编码私钥
  2. 使用环境变量管理敏感信息:
    npm install dotenv
    创建.env文件:
    PRIVATE_KEY=your_private_key
    然后在hardhat.config.js中:
    require('dotenv').config(); module.exports = { networks: { devnet: { accounts: [process.env.PRIVATE_KEY] } } };
  3. .env添加到.gitignore

10.2 合约安全测试

  1. 安装安全分析工具:
    npm install --save-dev @nomicfoundation/hardhat-verify npm install --save-dev solidity-coverage
  2. 配置Hardhat:
    require("@nomicfoundation/hardhat-verify"); require("solidity-coverage"); module.exports = { // ... etherscan: { apiKey: process.env.ETHERSCAN_API_KEY } };
  3. 运行安全分析:
    npx hardhat coverage

10.3 权限控制模式

即使在开发环境,也应该实现合理的权限控制:

// contracts/OwnableDemo.sol pragma solidity ^0.8.0; contract OwnableDemo { address public owner; constructor() { owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } function restrictedFunction() public onlyOwner { // 只有所有者能调用 } function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0), "Invalid address"); owner = newOwner; } }

10.4 升级模式实践

使用OpenZeppelin的升级插件实现可升级合约:

  1. 安装依赖:
    npm install @openzeppelin/hardhat-upgrades
  2. 配置Hardhat:
    require('@openzeppelin/hardhat-upgrades');
  3. 编写可升级合约:
    // contracts/UpgradeableDemo.sol pragma solidity ^0.8.0; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; contract UpgradeableDemo is Initializable { uint256 public value; function initialize(uint256 _value) public initializer { value = _value; } function setValue(uint256 _value) public { value = _value; } }
  4. 编写部署脚本:
    const { ethers, upgrades } = require("hardhat"); async function main() { const UpgradeableDemo = await ethers.getContractFactory("UpgradeableDemo"); const instance = await upgrades.deployProxy(UpgradeableDemo, [42]); await instance.deployed(); console.log("Proxy deployed to:", instance.address); }

11. 性能调优与压力测试

当你的DApp复杂度增加时,Devnet的性能调优就变得尤为重要。

11.1 基准测试方法

  1. 安装基准测试工具:
    npm install --save-dev @ethereumjs/vm @ethereumjs/block
  2. 创建测试脚本:
    const { VM } = require('@ethereumjs/vm'); const { Block } = require('@ethereumjs/block'); const Common = require('@ethereumjs/common').default; const common = new Common({ chain: 'mainnet' }); const vm = new VM({ common }); async function runBenchmark() { const block = Block.fromBlockData({}, { common }); const start = Date.now(); for (let i = 0; i < 1000; i++) { await vm.runBlock({ block }); } console.log(`Time taken: ${Date.now() - start}ms`); } runBenchmark();

11.2 Geth性能参数

优化Geth启动参数:

--cache 4096 \ --gcmode archive \ --txlookuplimit 0 \ --syncmode full \ --maxpeers 50 \ --metrics \ --pprof

关键参数说明:

  • --cache: 增加内存缓存大小,提高性能
  • --gcmode: 设置归档模式保留所有历史状态
  • --txlookuplimit: 禁用交易索引限制
  • --syncmode: 完整同步模式

11.3 负载测试工具

使用Hardhat的负载测试能力:

const { ethers } = require("hardhat"); async function loadTest() { const [deployer] = await ethers.getSigners(); const Contract = await ethers.getContractFactory("YourContract"); const contract = await Contract.deploy(); // 模拟100次连续调用 const start = Date.now(); for (let i = 0; i < 100; i++) { await contract.someFunction(); } const duration = Date.now() - start; console.log(`Average tx time: ${duration / 100}ms`); } loadTest();

11.4 状态快照管理

当Devnet运行时间较长后,状态数据会变得庞大。我们可以使用快照来管理:

  1. 创建定期快照:
    geth snapshot dump --datadir ./chaindata snapshot-$(date +%Y%m%d).json
  2. 恢复到特定快照:
    geth snapshot restore --datadir ./chaindata snapshot-20230801.json
  3. 自动化快照管理脚本:
    #!/bin/bash DATE=$(date +%Y%m%d) geth snapshot dump --datadir ./chaindata snapshot-$DATE.json find ./chaindata -name "snapshot-*.json" -mtime +7 -exec rm {} \;

12. 团队协作开发配置

当多个开发者共同使用一个Devnet环境时,需要一些额外的配置。

12.1 共享节点配置

  1. 将Geth节点作为服务运行:
    [Unit] Description=Ethereum Devnet Node After=network.target [Service] User=ubuntu ExecStart=/usr/bin/geth --datadir /path/to/chaindata \ --networkid 1337 \ --http \ --http.addr 0.0.0.0 \ --http.port 8545 \ --http.api "eth,net,web3,personal,miner" \ --allow-insecure-unlock \ --mine \ --miner.threads 1 Restart=always RestartSec=3 LimitNOFILE=4096 [Install] WantedBy=multi-user.target
  2. 启用服务:
    sudo systemctl enable geth-devnet sudo systemctl start geth-devnet

12.2 多开发者账户管理

  1. 在创世文件中预分配多个账户:
    "alloc": { "0xAccount1": { "balance": "1000000000000000000000" }, "0xAccount2": { "balance": "1000000000000000000000" }, "0xAccount3": { "balance": "1000000000000000000000" } }
  2. 为每个开发者创建独立的Hardhat配置:
    // hardhat.config.user1.js module.exports = { networks: { devnet: { url: "http://devnet.example.com:8545", accounts: ["0xuser1PrivateKey"] } } };
  3. 使用特定配置运行:
    npx hardhat run scripts/deploy.js --config hardhat.config.user1.js

12.3 文档与知识共享

  1. 创建项目README.md:
    # Ethereum Devnet 环境指南 ## 快速开始 1. 克隆仓库 2. 安装依赖:`npm install` 3. 启动Geth节点:`./scripts/start-devnet.sh` 4. 部署合约:`npx hardhat run scripts/deploy.js --network devnet` ## 账户管理 - 预分配账户: - 地址1: 0x... (私钥存储在1password) - 地址2: 0x... ## 常用命令 - 重启节点:`sudo systemctl restart geth-devnet` - 查看日志:`journalctl -u geth-devnet -f`
  2. 使用Swagger记录API:
    # swagger.yaml openapi: 3.0.0 info: title: Devnet API version: 1.0.0 paths: /: post: summary: JSON-RPC endpoint requestBody: content: application/json: schema: type: object properties: jsonrpc: type: string method: type: string params: type: array id: type: integer responses: '200': description: JSON-RPC response

12.4 持续集成流程

扩展之前的CI配置以支持团队开发:

name: Team Devnet CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest strategy: matrix: node-version: [16.x] network: [devnet1, devnet2] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} - name: Install dependencies run: npm install - name: Start Geth ${{ matrix.network }} run: | geth init --datadir ./chaindata genesis-${{ matrix.network }}.json geth --datadir ./chaindata \ --networkid ${{ matrix.network == 'devnet1' && '1337' || '31337' }} \ --http --http.addr 0.0.0.0 --http.port 8545 \ --http.api "eth,net,web3,personal" \ --allow-insecure-unlock \ --mine --miner.threads 1 & - name: Run tests run: npx hardhat test --network ${{ matrix.network }} - name: Run linter run: npx eslint '**/*.js'

13. 真实项目案例实践

让我们通过一个真实的DeFi项目案例来演示如何在Devnet环境中开发和测试。

13.1 创建ERC20代币

// contracts/MyToken.sol pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MyToken is ERC20 { constructor(uint256 initialSupply) ERC20("MyToken", "MTK") { _mint(msg.sender, initialSupply); } }

部署脚本:

// scripts/deploy-token.js async function main() { const [deployer] = await ethers.getSigners(); console.log("Deploying contracts with account:", deployer.address); const MyToken = await ethers.getContractFactory("MyToken"); const token = await MyToken.deploy(ethers.utils.parseEther("1000000")); console.log("Token deployed to:", token.address); }

13.2 开发质押合约

// contracts/Staking.sol pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract Staking is ReentrancyGuard { IERC20 public token; mapping(address => uint256) public stakedBalances; mapping(address => uint256) public rewardBalances; mapping(address => uint256) public lastStakedTime; uint256 public constant REWARD_RATE = 10; // 10% per year constructor(address _token) { token = IERC20(_token); } function stake(uint256 amount) external nonReentrant { require(amount > 0, "Amount must be positive"); // 先发放之前的奖励 if (stakedBalances[msg.sender] > 0) { uint256 reward = calculateReward(msg.sender); rewardBalances[msg.sender] += reward; } token.transferFrom(msg.sender, address(this), amount); stakedBalances[msg.sender] += amount; lastStakedTime[msg.sender] = block.timestamp; } function calculateReward(address user) public view returns (uint256) { if (stakedBalances[user] == 0) return 0; uint256 stakedTime = block.timestamp - lastStakedTime[user]; return stakedBalances[user] * REWARD_RATE * stakedTime / (365 days * 100); } function claimReward() external nonReentrant { uint256 reward = rewardBalances[msg.sender] + calculateReward(msg.sender); require(reward > 0, "No reward to claim"); rewardBalances[msg.sender] = 0; lastStakedTime[msg.sender] = block.timestamp; token.transfer(msg.sender, reward); } function unstake(uint256 amount) external nonReentrant { require(amount > 0 && amount <= stakedBalances[msg.sender], "Invalid amount"); // 先发放奖励 uint256 reward = calculateReward(msg.sender); rewardBalances[msg.sender] += reward; stakedBalances[msg.sender] -= amount; lastStakedTime[msg.sender] = block.timestamp; token.transfer(msg.sender, amount); } }

13.3 编写完整测试套件

// test/Staking.test.js const { expect } = require("chai"); const { ethers } = require("hardhat"); describe("Staking System", function () { let token, staking, owner, user1, user2; beforeEach(async function () { [owner, user1, user2] = await ethers.getSigners(); const MyToken = await ethers.getContractFactory("MyToken"); token = await MyToken.deploy(ethers.utils.parseEther("1000000")); const Staking = await ethers.getContractFactory("Staking"); staking = await Staking.deploy(token.address); // 给用户分配代币 await token.transfer(user1.address, ethers.utils.parseEther("1000")); await token.transfer(user2.address, ethers.utils.parseEther("1000")); }); it("should allow users to stake tokens", async function
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 13:25:14

解密《动手学深度学习-pytorch》中#@save标记的实战意义与封装逻辑

1. #save标记的双重身份&#xff1a;从代码封装到教学理念 第一次翻开《动手学深度学习-pytorch》时&#xff0c;我和大多数读者一样&#xff0c;对函数定义后面那个神秘的#save标记充满好奇。经过反复实践和源码追踪&#xff0c;我发现这个小标记背后藏着作者精心设计的双重逻…

作者头像 李华
网站建设 2026/4/15 13:22:10

Android串口通信 串口开发 serialport 多串口

在 Android 上进行串口通信,通常使用 **android-serialport-api**(基于 JNI 访问 Linux TTY 设备)。对于多串口场景,核心思路是**为每个串口独立维护一个 `SerialPort` 实例**,并分别管理各自的输入/输出流及读取线程。 下面给出完整的多串口开发方案,包括依赖配置、权限…

作者头像 李华
网站建设 2026/4/15 13:22:10

embedded-hal 错误处理最佳实践:从基础模式到高级策略

embedded-hal 错误处理最佳实践&#xff1a;从基础模式到高级策略 【免费下载链接】embedded-hal A Hardware Abstraction Layer (HAL) for embedded systems 项目地址: https://gitcode.com/gh_mirrors/em/embedded-hal 在嵌入式系统开发中&#xff0c;错误处理是确保系…

作者头像 李华
网站建设 2026/4/15 13:21:10

Simulink延迟触发模块实战:从信号检测到计时器应用

1. Simulink延迟触发模块的核心应用场景 在工业自动化和嵌入式系统开发中&#xff0c;延迟触发功能就像是个智能的"时间守门员"。我做过一个机器人控制项目&#xff0c;需要检测传感器信号稳定持续2秒后才允许执行动作&#xff0c;这个场景就是典型的延迟触发应用。S…

作者头像 李华
网站建设 2026/4/15 13:16:45

React Native Permissions最佳实践:避免常见陷阱的完整清单

React Native Permissions最佳实践&#xff1a;避免常见陷阱的完整清单 【免费下载链接】react-native-permissions An unified permissions API for React Native on iOS, Android and Windows. 项目地址: https://gitcode.com/gh_mirrors/re/react-native-permissions …

作者头像 李华
网站建设 2026/4/15 13:16:07

图形学进阶|时间抗锯齿(TAA)实战解析

1. 时间抗锯齿&#xff08;TAA&#xff09;的核心原理 当你第一次在游戏中开启TAA时&#xff0c;可能会觉得画面突然变得"柔和"了。这不是错觉&#xff0c;而是TAA正在发挥它的魔法。想象一下&#xff0c;你正在用手机拍摄高速旋转的电风扇叶片。单张照片里叶片边缘会…

作者头像 李华