nasirovpe/solidity-auction-v2
GitHub: nasirovpe/solidity-auction-v2
基于 Solidity 的安全以太坊拍卖智能合约,实现保留价、第二价格出价、拉取支付与重入保护,并附带完整的 Hardhat 测试套件。
Stars: 1 | Forks: 0
# AuctionV2
安全的 Ethereum 拍卖智能合约,具备保留价、第二价格出价、拉取支付提取以及 Hardhat 测试覆盖。
## 描述
**AuctionV2** 是一个针对单个物品的链上拍卖(通过 IPFS 元数据哈希标识)。出价者在固定的时间窗口内竞争。该合约强制执行**保留价**和**最低加价幅度**,应用**第二价格风格的约束性出价**,并通过**拉取支付**结算资金,因此退款和收益是被显式领取的,而不是在嵌套调用中被动推送。
该代码库包含 Hardhat 工具链、OpenZeppelin **ReentrancyGuard** 以及一个自动化测试套件(**13 个测试**,全部通过)。
## 功能
- **保留价** — 第一次出价必须达到或超过配置的最低销售价格。
- **加价幅度** — 随后的每一次出价必须至少等于当前最高出价加上加价幅度。
- **第二价格风格逻辑** — 获胜者的约束金额遵循保留价和加价规则(参见[合约概述](#contract-overview))。
- **拉取支付提取** — 被超出价的竞拍者、获胜者的多余金额以及所有者的收益均通过专用函数提取。
- **使用 `call` 代替 `transfer`** — ETH 发送使用底层 `call` 并带有显式的失败处理(兼容智能合约钱包以及合并后的 gas 机制)。
- **所有者无法出价** — 部署者被阻止调用 `placeBid` 以避免自我交易。
- **重入保护** — 关键的状态更改路径使用 OpenZeppelin `ReentrancyGuard`。
- **拍卖生命周期** — 包含运行中、已结束和已取消状态;所有者只能在第一次出价之前且在 `endTime` 之前取消。
- **Hardhat 测试** — 全面的场景覆盖,包括出价、结算和访问控制。
## 技术栈
| 层级 | 技术 |
|--------|------------|
| 语言 | Solidity `^0.8.20` |
| 框架 | [Hardhat](https://hardhat.org/) `^2.26` |
| 库 | [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts/) `^5.0` (`ReentrancyGuard`) |
| 测试 | Hardhat + Ethers v6 + Chai matchers + `@nomicfoundation/hardhat-network-helpers` |
| 模块系统 | ESM (`package.json` 中为 `"type": "module"`;Hardhat 配置为 `hardhat.config.cjs`) |
## 合约概述
**主合约:** `contracts/AuctionV2.sol`
### 构造函数参数
| 参数 | 描述 |
|-----------|-------------|
| `_ipfsHash` | 拍卖物品的元数据 URI (例如 `ipfs://...`) |
| `_durationSeconds` | 从部署区块时间戳起计算的拍卖时长;必须 > 0 |
| `_reservePrice` | 以 wei 为单位的最低销售价格;第一次出价必须 ≥ 此值 |
| `_bidIncrement` | 在当前最高出价之上以 wei 为单位的最低加价金额 |
### 主要函数
| 函数 | 用途 |
|----------|---------|
| `placeBid()` | 在拍卖运行期间且在 `[startTime, endTime]` 之内提交应付出价 |
| `endAuction()` | 任何人都可以在 `endTime` 之后结束拍卖;将获胜者的多余金额记入 `pendingWithdrawals` |
| `cancelAuction()` | 仅限所有者;仅在没有出价且在 `endTime` 之前 |
| `withdraw()` | 在 `endAuction` 之后拉取退款(被超出价的竞拍者)或获胜者多余的金额 |
| `withdrawOwnerProceeds()` | 拍卖结束后,所有者一次性拉取 `highestBindingBid` |
| `isOpenForBids()` | 用于查看当前是否允许出价的辅助视图函数 |
### 约束性出价行为(摘要)
- **第一次出价:** 如果满足 `reservePrice`,`highestBindingBid` 将被设置为 `reservePrice`。
- **后续出价:** 前一个最高出价者的全部出价金额将被记入 `pendingWithdrawals`;`highestBindingBid` 将使用第二价格风格的规则进行更新(受限于新出价以及先前出价 + 增量)。
- **结束之后:** 获胜者通过 `withdrawOwnerProceeds` 将约束金额支付给所有者;任何高于约束性出价的金额都将由获胜者通过 `withdraw` 提取。
代码库中保留了一份较早的 **`Auction.sol`** 合约以供参考;**AuctionV2** 是有测试覆盖并推荐使用的版本。
## 安装
```
git clone
cd auction-smart-contract
npm install
```
在添加部署或网络脚本时复制环境变量(切勿提交密钥):
```
# .env — 仅供参考;文件已被 gitignore
# PRIVATE_KEY=...
# RPC_URL=...
```
## 编译
```
npm run compile
# 或
npx hardhat compile
```
编译后的产物将写入 `artifacts/` 和 `cache/`(两者均被 Git 忽略)。
## 测试
```
npm test
# 或
npx hardhat test
```
预期结果:`test/AuctionV2.test.js` 中有 **13 个通过**的测试。
## 部署参数(示例)
没有捆绑的部署脚本;请使用 Hardhat/ethers 并使用与测试夹具相同的值进行部署:
```
import hre from "hardhat";
const { ethers } = hre;
const ipfsHash = "ipfs://auction-metadata";
const durationSeconds = 3600; // 1 hour
const reservePrice = ethers.parseEther("0.05");
const bidIncrement = ethers.parseEther("0.01");
const AuctionV2 = await ethers.getContractFactory("AuctionV2");
const auction = await AuctionV2.deploy(
ipfsHash,
durationSeconds,
reservePrice,
bidIncrement
);
await auction.waitForDeployment();
console.log("AuctionV2 deployed to:", await auction.getAddress());
```
根据您的销售情况调整 `durationSeconds`、`reservePrice` 和 `bidIncrement`。部署者将成为 `owner`,并且无法调用 `placeBid`。
## 安全提示
- **未经审计。** 此代码适用于学习和集成测试;在主网上投入真实价值之前,请获取专业审计。
- **重入** — `placeBid`、`cancelAuction`、`withdraw` 和 `withdrawOwnerProceeds` 使用了 `nonReentrant`。在扩展合约时,请遵循检查-生效-交互模式。
- **拉取支付** — 降低了在出价更新期间因推送 ETH 而带来的重入风险;用户必须调用 `withdraw` 才能收到资金。
- **所有者信任** — 所有者只能在任何出价之前取消;在有出价之后,取消功能将被禁用。成功出售后的收益仅限于 `highestBindingBid`。
- **时间依赖性** — 出价和 `endAuction` 依赖于 `block.timestamp`;矿工/验证者的回旋余地有限——请据此设计持续时间。
- **不可升级** — 该合约未使用代理模式;不正确的构造函数参数是不可变的。
- **密钥** — 将私钥和 RPC URL 保存在 `.env` 中(已在 `.gitignore` 中列出)。
## 文件夹结构
```
.
├── contracts/
│ ├── AuctionV2.sol # Main auction contract (tested)
│ └── Auction.sol # Earlier version (reference)
├── test/
│ └── AuctionV2.test.js # Hardhat test suite
├── hardhat.config.cjs # Solidity 0.8.20, optimizer, Hardhat plugins
├── package.json
├── package-lock.json
├── .gitignore # node_modules, artifacts, cache, .env, coverage, etc.
└── README.md
```
生成的及本地专属路径(未提交):`node_modules/`、`artifacts/`、`cache/`、`coverage/`、`coverage.json`。
## 许可证
本项目的智能合约基于 **MIT License** 发布,如源文件中的 SPDX 标识符 (`// SPDX-License-Identifier: MIT`) 所示。
特此免费授予任何获得本软件及相关文档文件(“软件”)副本的人不受限制地处理本软件的权利,包括但不限于使用、复制、修改、合并、发布、分发、再授权和/或销售本软件副本的权利,前提是上述版权声明和本许可声明包含在所有副本或软件的绝大部分中。
本软件按“原样”提供,不提供任何形式的明示或暗示的保证,包括但不限于适销性、特定用途适用性和不侵权的保证。在任何情况下,对于因本软件或本软件的使用或其他交易引起、产生或相关的任何索赔、损害或其他责任,作者或版权所有者均不承担任何责任,无论是在合同诉讼、侵权行为还是其他诉讼中。
标签:Hardhat, Solidity, 以太坊, 区块链, 拍卖合约, 数据可视化, 智能合约