探索Web3新速度:Sonic高性能Layer-1上的BlindAuction智能合约实践在Web3的浪潮中,Sonic以其超高吞吐量(400,000+TPS)和亚秒级确认速度,正在重新定义区块链的性能边界。作为一款高性能EVMLayer-1区块链,Sonic为去中心化金融(DeFi)提供了无
在Web3的浪潮中,Sonic以其超高吞吐量(400,000+ TPS)和亚秒级确认速度,正在重新定义区块链的性能边界。作为一款高性能EVM Layer-1区块链,Sonic为去中心化金融(DeFi)提供了无限可能。本文将带你走进Sonic生态,结合一个经典的BlindAuction(盲拍)智能合约,深入解析如何在Sonic上开发、测试和部署智能合约,体验Web3的极致速度与安全性!
Sonic是一款专为DeFi打造的高性能EVM Layer-1区块链,支持超400,000 TPS和亚秒级交易确认,结合其原生代币S的质押与治理机制,为开发者提供高效、安全的开发环境。本文通过实现一个BlindAuction智能合约,详细展示了从项目初始化、合约编写、全面测试到在Sonic测试网部署的全流程。盲拍合约通过加密出价、阶段控制和资金安全机制,保障了公平竞拍和隐私保护。测试覆盖率达100%,并成功部署于Sonic和Hoodi测试网,展现了Sonic在Web3开发中的强大潜力。
Sonic 是一款高性能的 EVM Layer-1 区块链,旨在通过其速度、激励机制和世界级基础设施,支持下一代去中心化金融(DeFi)应用的发展。根据 Sonic | The Fastest EVM Layer-1 Blockchain,它提供超过 400,000 TPS(每秒交易量)和亚秒级确认,确保快速、安全的交易体验。
S 代币是 Sonic 的原生代币,主要用于支付交易费用、质押以保护网络、运行验证节点以及参与治理决策。根据 Sonic $S Tokenomics Explained: Utility, Staking, and Rewards,这些功能增强了网络的去中心化和可持续性。
mcd BlindAuction # mkdir BlindAuction && cd BlindAuction
forge init --template https://github.com/qiaopengjun5162/foundry-template
BlindAuction on main [✘!?] on 🐳 v28.2.2 (orbstack)
➜ tree . -L 6 -I "coverage_report|lib|.vscode|out|lcov.info|bin|cache"
.
├── _typos.toml
├── CHANGELOG.md
├── cliff.toml
├── docs
│ └── readme.md
├── foundry_monad.toml
├── foundry.toml
├── LICENSE
├── README.md
├── remappings.txt
├── script
│ ├── BlindAuction.s.sol
│ └── deploy.sh
├── slither.config.json
├── src
│ └── BlindAuction.sol
├── style_guide.md
└── test
└── BlindAuction.t.sol
5 directories, 15 files
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint256 deposit;
}
address payable public beneficiary;
uint256 public biddingEnd;
uint256 public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint256 public highestBid;
mapping(address => uint256) public lockedFunds;
mapping(address => uint256) public pendingReturns;
event AuctionEnded(address winner, uint256 highestBid);
error TooEarly(uint256 time);
error TooLate(uint256 time);
error AuctionEndAlreadyCalled();
error InvalidBidReveal();
error FundsNotLockedCorrectly();
modifier onlyBefore(uint256 time) {
if (block.timestamp >= time) revert TooLate(time);
_;
}
modifier onlyAfter(uint256 time) {
if (block.timestamp <= time) revert TooEarly(time);
_;
}
constructor(uint256 biddingTime, uint256 revealTime, address payable beneficiaryAddress) {
if (beneficiaryAddress == address(0)) revert InvalidBidReveal();
beneficiary = beneficiaryAddress;
biddingEnd = block.timestamp + biddingTime;
revealEnd = biddingEnd + revealTime;
}
function bid(bytes32 blindedBid) external payable onlyBefore(biddingEnd) {
if (msg.value == 0) revert InvalidBidReveal();
bids[msg.sender].push(Bid({blindedBid: blindedBid, deposit: msg.value}));
}
function reveal(uint256[] calldata values, bool[] calldata fakes, bytes32[] calldata secrets)
external
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint256 length = bids[msg.sender].length;
require(values.length == length, "Values length mismatch");
require(fakes.length == length, "Fakes length mismatch");
require(secrets.length == length, "Secrets length mismatch");
uint256 refund;
for (uint256 i = 0; i < length; i++) {
Bid storage bidToCheck = bids[msg.sender][i];
(uint256 value, bool fake, bytes32 secret) = (values[i], fakes[i], secrets[i]);
if (bidToCheck.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
continue;
}
refund += bidToCheck.deposit;
if (!fake && bidToCheck.deposit >= value) {
if (placeBid(msg.sender, value)) {
refund -= value;
lockedFunds[msg.sender] = value;
}
}
bidToCheck.blindedBid = bytes32(0);
}
if (refund > 0) {
(bool success,) = payable(msg.sender).call{value: refund}("");
require(success, "Refund failed");
}
}
function withdraw() external {
uint256 amount = pendingReturns[msg.sender];
if (amount > 0) {
pendingReturns[msg.sender] = 0;
(bool success,) = payable(msg.sender).call{value: amount}("");
require(success, "Withdrawal failed");
}
}
function auctionEnd() external onlyAfter(revealEnd) {
if (ended) revert AuctionEndAlreadyCalled();
ended = true;
emit AuctionEnded(highestBidder, highestBid);
// 只转移最高出价资金,确保资金已被锁定
if (lockedFunds[highestBidder] != highestBid) {
revert FundsNotLockedCorrectly();
}
(bool success,) = beneficiary.call{value: highestBid}("");
require(success, "Transfer failed");
}
function placeBid(address bidder, uint256 value) internal returns (bool success) {
if (value <= highestBid) {
return false;
}
// 保存当前最高出价信息
address previousHighestBidder = highestBidder;
uint256 previousHighestBid = highestBid;
// 更新最高出价
highestBid = value;
highestBidder = bidder;
// 锁定前一个最高出价者的资金
if (previousHighestBidder != address(0)) {
pendingReturns[previousHighestBidder] += previousHighestBid;
}
return true;
}
}
这个智能合约实现了一个盲拍(Blind Auction)系统,分为投标和揭示两个阶段。在投标阶段(biddingEnd
截止前),参与者通过bid
函数提交加密的出价哈希值blindedBid
并附带资金押金。进入揭示阶段(biddingEnd
到revealEnd
),参与者调用reveal
函数公开实际出价金额、验证码和真伪标志,合约校验哈希匹配后,若出价真实且押金充足,则激活placeBid
更新最高出价(原最高出价者资金进入待退款pendingReturns
)。拍卖结束后,通过auctionEnd
将最高出价资金(需通过lockedFunds
校验)转给受益人beneficiary
,其他参与者可通过withdraw
提取退款。关键特性包括:
onlyBefore
/onlyAfter
)严格划分投标、揭示和结束时段示例流程:A提交加密出价(实际100ETH)并质押120ETH → 揭示阶段A验证出价 → 若成为最高出价,100ETH被锁定、20ETH退回 → 拍卖结束受益人获得100ETH。
这是一个密封拍卖(盲拍)智能合约,核心功能是让竞标者在不暴露出价的前提下竞价。
sequenceDiagram
participant 竞标者 as 竞标者
participant 合约 as BlindAuction
participant 受益人 as 受益人
竞标者->>合约: 1. 提交加密出价(blindedBid)
Note right of 合约: 出价阶段(所有人只看到哈希值)
loop 隐藏竞价
合约->>合约: 存储加密出价和押金
end
竞标者->>合约: 2. 揭示真实出价(reveal)
Note right of 合约: 揭示阶段(提交真实值+密码)
alt 有效出价
合约->>合约: 检查哈希匹配
合约->>合约: 更新最高出价
else 无效出价
合约->>竞标者: 退还押金
end
合约->>受益人: 3. 结束拍卖(auctionEnd)
Note left of 受益人: 获得最高出价金额
合约->>竞标者: 退还落标者押金
graph LR
A[加密提交] --> B[哈希盲拍]
C[多重验证] --> D[防作弊机制]
E[资金安全] --> F[独立退款通道]
B -.-> |任何人无法预知出价| G(公平竞拍)
D -.-> |无效报价自动退还| H(防欺诈)
F -.-> |失败竞价原路返还| I(资金安全)
💡 就像「暗拍」游戏:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {Test, console} from "forge-std/Test.sol";
import {BlindAuction} from "../src/BlindAuction.sol";
contract BlindAuctionTest is Test {
BlindAuction public auction;
address payable public beneficiary = payable(address(0xBEEF));
uint256 public constant BIDDING_TIME = 1 hours;
uint256 public constant REVEAL_TIME = 1 hours;
// Test accounts
address public bidder1 = makeAddr("bidder1");
address public bidder2 = makeAddr("bidder2");
address public bidder3 = makeAddr("bidder3");
/**
* Account bidder1 = makeAccount("bidder1");
* Account bidder2 = makeAccount("bidder2");
* Account bidder3 = makeAccount("bidder3");
*
*
* // 使用账户生成器创建可追踪的测试账户
* address public bidder1 = makeAddr("bidder1");
* address public bidder2 = makeAddr("bidder2");
* address public bidder3 = makeAddr("bidder3");
*/
function setUp() public {
auction = new BlindAuction(BIDDING_TIME, REVEAL_TIME, beneficiary);
}
// ================ 核心功能测试 ================ //
function testContractInitialization() public view {
assertEq(auction.beneficiary(), beneficiary);
assertEq(auction.biddingEnd(), block.timestamp + BIDDING_TIME);
assertEq(auction.revealEnd(), block.timestamp + BIDDING_TIME + REVEAL_TIME);
assertFalse(auction.ended());
}
function testValidBid() public {
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, "secret"));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
assertEq(address(auction).balance, 1 ether);
}
function testRevealSingleValidBid() public {
// Submit bid
uint256 value = 1 ether;
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(value, false, secret));
vm.deal(bidder1, 2 ether);
vm.prank(bidder1);
auction.bid{value: value}(blindedBid);
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Reveal bid
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](1);
bytes32[] memory secrets = new bytes32[](1);
(values[0], fakes[0], secrets[0]) = (value, false, secret);
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
assertEq(auction.highestBidder(), bidder1);
assertEq(auction.highestBid(), value);
}
// 基础状态测试
function testInitialState() public view {
assertEq(auction.beneficiary(), beneficiary);
assertEq(auction.biddingEnd(), block.timestamp + BIDDING_TIME);
assertEq(auction.revealEnd(), block.timestamp + BIDDING_TIME + REVEAL_TIME);
assertFalse(auction.ended());
}
// 有效出价测试
function testBidBeforeEnd() public {
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, "secret"));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
assertEq(address(auction).balance, 1 ether);
}
// 超时出价测试
function test_RevertWhen_BidAfterBiddingEnd() public {
vm.warp(block.timestamp + BIDDING_TIME + 1); // 时间旅行到出价结束后
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, "secret"));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
vm.expectRevert(abi.encodeWithSelector(BlindAuction.TooLate.selector, auction.biddingEnd()));
auction.bid{value: 1 ether}(blindedBid);
}
// 单揭示测试
function testRevealSingleBid() public {
// Place bid
uint256 value = 1 ether;
bool fake = false;
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(value, fake, secret));
vm.deal(bidder1, 2 ether);
vm.prank(bidder1);
auction.bid{value: value}(blindedBid);
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1); // 进入揭示阶段
// Reveal // 准备揭示参数
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](1);
bytes32[] memory secrets = new bytes32[](1);
values[0] = value;
fakes[0] = fake;
secrets[0] = secret;
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
assertEq(auction.highestBidder(), bidder1);
assertEq(auction.highestBid(), value);
}
// 混合揭示测试(真实出价+虚假出价)
function testRevealWithFakeBid() public {
// 设置出价参数
// Place real bid
uint256 realValue = 1 ether;
bool realFake = false;
bytes32 realSecret = keccak256("real");
bytes32 realBlindedBid = keccak256(abi.encodePacked(realValue, realFake, realSecret));
// Place fake bid
uint256 fakeValue = 2 ether;
bool fakeFake = true;
bytes32 fakeSecret = keccak256("fake");
bytes32 fakeBlindedBid = keccak256(abi.encodePacked(fakeValue, fakeFake, fakeSecret));
vm.deal(bidder1, realValue + fakeValue);
vm.startPrank(bidder1);
// 在转账前记录初始余额
uint256 initialTotalBalance = bidder1.balance;
assertEq(initialTotalBalance, realValue + fakeValue);
auction.bid{value: realValue}(realBlindedBid);
auction.bid{value: fakeValue}(fakeBlindedBid);
vm.stopPrank();
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1); // 进入揭示阶段
// 准备揭示参数
// Reveal both bids
uint256[] memory values = new uint256[](2);
bool[] memory fakes = new bool[](2);
bytes32[] memory secrets = new bytes32[](2);
(values[0], fakes[0], secrets[0]) = (realValue, false, realSecret);
(values[1], fakes[1], secrets[1]) = (fakeValue, true, fakeSecret);
uint256 initialBalance = bidder1.balance;
assertEq(initialBalance, 0);
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
// Should only place the real bid
assertEq(auction.highestBidder(), bidder1);
assertEq(auction.highestBid(), realValue);
assertEq(bidder1.balance, initialTotalBalance - realValue);
}
// 提款功能测试
function testWithdraw() public {
// 设置两个竞标者
// Setup two bidders where bidder2 outbids bidder1
uint256 value1 = 1 ether;
bytes32 secret1 = keccak256("secret1");
bytes32 blindedBid1 = keccak256(abi.encodePacked(value1, false, secret1));
uint256 value2 = 2 ether;
bytes32 secret2 = keccak256("secret2");
bytes32 blindedBid2 = keccak256(abi.encodePacked(value2, false, secret2));
// 提交出价
vm.deal(bidder1, value1);
vm.deal(bidder2, value2);
vm.prank(bidder1);
auction.bid{value: value1}(blindedBid1);
vm.prank(bidder2);
auction.bid{value: value2}(blindedBid2);
// Move to reveal phase and reveal
vm.warp(block.timestamp + BIDDING_TIME + 1); // 进入揭示阶段
// 揭示出价
uint256[] memory values1 = new uint256[](1);
bool[] memory fakes1 = new bool[](1);
bytes32[] memory secrets1 = new bytes32[](1);
values1[0] = value1;
fakes1[0] = false;
secrets1[0] = secret1;
vm.prank(bidder1);
auction.reveal(values1, fakes1, secrets1);
uint256[] memory values2 = new uint256[](1);
bool[] memory fakes2 = new bool[](1);
bytes32[] memory secrets2 = new bytes32[](1);
values2[0] = value2;
fakes2[0] = false;
secrets2[0] = secret2;
vm.prank(bidder2);
auction.reveal(values2, fakes2, secrets2);
// bidder1 should have pending return
uint256 initialBalance = bidder1.balance;
vm.prank(bidder1);
auction.withdraw();
assertEq(bidder1.balance, initialBalance + value1);
}
// 提款功能测试
function testWithdraw2() public {
// 设置两个竞标者
uint256 value1 = 1 ether;
bytes32 secret1 = keccak256("secret1");
bytes32 blindedBid1 = keccak256(abi.encodePacked(value1, false, secret1));
uint256 value2 = 2 ether;
bytes32 secret2 = keccak256("secret2");
bytes32 blindedBid2 = keccak256(abi.encodePacked(value2, false, secret2));
// 提交出价
vm.deal(bidder1, value1);
vm.deal(bidder2, value2);
vm.prank(bidder1);
auction.bid{value: value1}(blindedBid1);
vm.prank(bidder2);
auction.bid{value: value2}(blindedBid2);
vm.warp(block.timestamp + BIDDING_TIME + 1); // 进入揭示阶段
// 揭示出价
vm.prank(bidder1);
auction.reveal(_toArray(value1), _toArray(false), _toArray(secret1));
vm.prank(bidder2);
auction.reveal(_toArray(value2), _toArray(false), _toArray(secret2));
// 提款验证
uint256 initialBalance = bidder1.balance;
vm.prank(bidder1);
auction.withdraw();
assertEq(bidder1.balance, initialBalance + value1); // 验证退款金额
}
function test_RevertWhen_AuctionEndBeforeRevealEnd() public {
vm.warp(block.timestamp + BIDDING_TIME + 1);
vm.expectRevert(abi.encodeWithSelector(BlindAuction.TooEarly.selector, auction.revealEnd()));
auction.auctionEnd();
}
// 重复结束拍卖测试
function test_RevertWhen_AuctionEndTwice() public {
// Setup and complete auction
uint256 value = 1 ether;
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(value, false, secret));
vm.deal(bidder1, value);
vm.prank(bidder1);
auction.bid{value: value}(blindedBid);
vm.warp(block.timestamp + BIDDING_TIME + 1);
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](1);
bytes32[] memory secrets = new bytes32[](1);
values[0] = value;
fakes[0] = false;
secrets[0] = secret;
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
// vm.warp(block.timestamp + REVEAL_TIME + 1);
vm.warp(auction.biddingEnd() + auction.revealEnd() + 1);
auction.auctionEnd();
// Second call should fail
vm.expectRevert(abi.encodeWithSelector(BlindAuction.AuctionEndAlreadyCalled.selector));
auction.auctionEnd();
}
// 无效揭示测试
function testRevealInvalidBid() public {
// Place bid with invalid reveal data
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, "real"));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Try to reveal with wrong data
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](1);
bytes32[] memory secrets = new bytes32[](1);
values[0] = 1 ether;
fakes[0] = false;
secrets[0] = keccak256("wrong");
uint256 initialBalance = bidder1.balance;
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
// Should refund deposit but not place bid
assertEq(auction.highestBidder(), address(0));
assertEq(auction.highestBid(), 0);
assertEq(bidder1.balance, initialBalance);
}
function testMultipleBidsSingleReveal() public {
// 记录初始余额
uint256 initialTotalBalance = bidder1.balance;
// 定义 bytes32 类型的 secret
bytes32 secret1 = keccak256(abi.encodePacked("secret1"));
bytes32 secret2 = keccak256(abi.encodePacked("secret2"));
// 计算正确的哈希值
bytes32 realBidHash = keccak256(abi.encodePacked(uint256(1 ether), false, secret1));
bytes32 fakeBidHash = keccak256(abi.encodePacked(uint256(2 ether), true, secret2));
// 提交两个出价
vm.deal(bidder1, 3 ether);
vm.startPrank(bidder1);
auction.bid{value: 1 ether}(realBidHash); // 真实出价
auction.bid{value: 2 ether}(fakeBidHash); // 虚假出价
vm.stopPrank();
// 确认资金已转入合约
assertEq(address(auction).balance, 3 ether, "Contract should receive 3 ether");
// 进入揭示阶段
vm.warp(block.timestamp + BIDDING_TIME + 1);
// 准备揭示数据 - 确保顺序与出价一致
uint256[] memory values = new uint256[](2);
bool[] memory fakes = new bool[](2);
bytes32[] memory secrets = new bytes32[](2);
// 第一个出价:真实出价1 ETH
values[0] = 1 ether;
fakes[0] = false;
secrets[0] = secret1; // 使用相同的 bytes32 secret
// 第二个出价:虚假出价2 ETH
values[1] = 2 ether;
fakes[1] = true;
secrets[1] = secret2; // 使用相同的 bytes32 secret
// 执行揭示
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
// 验证结果
assertEq(auction.highestBidder(), bidder1, "Highest bidder should be bidder1");
assertEq(auction.highestBid(), 1 ether, "Highest bid should be 1 ether");
// 验证资金流动
assertEq(
bidder1.balance,
initialTotalBalance + 2 ether, // 初始余额 + 虚假出价退款
"Balance after reveal should include fake bid refund"
);
// 验证合约锁定资金
assertEq(address(auction).balance, 1 ether, "Contract should keep 1 ether (real bid)");
// 验证锁定资金记录
assertEq(auction.lockedFunds(bidder1), 1 ether, "Bidder1 should have 1 ether locked");
}
// ================ 边界条件测试 ================ //
function testLowestPossibleBid() public {
vm.deal(bidder1, 1 wei);
vm.prank(bidder1);
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 wei), false, "min"));
auction.bid{value: 1 wei}(blindedBid);
assertEq(address(auction).balance, 1 wei);
}
function testInsufficientDepositForBid() public {
uint256 value = 1 ether;
bytes32 secret = keccak256("insufficient");
bytes32 blindedBid = keccak256(abi.encodePacked(value, false, secret));
vm.deal(bidder1, value - 1 wei); // Send 1 wei less than required
vm.prank(bidder1);
auction.bid{value: value - 1 wei}(blindedBid);
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Prepare reveal data
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](1);
bytes32[] memory secrets = new bytes32[](1);
(values[0], fakes[0], secrets[0]) = (value, false, secret);
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
// Bid should not be accepted
assertEq(auction.highestBidder(), address(0));
assertEq(auction.highestBid(), 0);
}
function testBidNotHigherThanCurrent() public {
// Submit first bid (1 ETH)
uint256 value1 = 1 ether;
bytes32 secret1 = keccak256("first");
bytes32 blindedBid1 = keccak256(abi.encodePacked(value1, false, secret1));
// Submit second bid (0.5 ETH, lower)
uint256 value2 = 0.5 ether;
bytes32 secret2 = keccak256("second");
bytes32 blindedBid2 = keccak256(abi.encodePacked(value2, false, secret2));
vm.deal(bidder1, value1);
vm.deal(bidder2, value2);
vm.prank(bidder1);
auction.bid{value: value1}(blindedBid1);
vm.prank(bidder2);
auction.bid{value: value2}(blindedBid2);
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Reveal first bid
vm.prank(bidder1);
auction.reveal(_toArray(value1), _toArray(false), _toArray(secret1));
// Reveal second bid
uint256 initialBalance = bidder2.balance;
vm.prank(bidder2);
auction.reveal(_toArray(value2), _toArray(false), _toArray(secret2));
// Validate state
assertEq(auction.highestBidder(), bidder1);
assertEq(auction.highestBid(), value1);
assertEq(bidder2.balance, initialBalance + value2); // Full refund
}
// ================ 异常处理测试 ================ //
function testInvalidReveal() public {
// Submit valid bid
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, "valid"));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Try to reveal with invalid data
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](1);
bytes32[] memory secrets = new bytes32[](1);
(values[0], fakes[0], secrets[0]) = (1 ether, false, keccak256("invalid"));
uint256 initialBalance = bidder1.balance;
vm.prank(bidder1);
auction.reveal(values, fakes, secrets);
// Should not accept invalid reveal
assertEq(auction.highestBidder(), address(0));
assertEq(auction.highestBid(), 0);
assertEq(bidder1.balance, initialBalance); // Deposit remains in contract
}
function testWithdrawFunds() public {
// Setup two bidders
uint256 value1 = 1 ether;
uint256 value2 = 2 ether;
address loser = bidder1;
address winner = bidder2;
// 记录初始余额
uint256 initialLoserTotalBalance = loser.balance;
uint256 initialWinnerTotalBalance = winner.balance;
// 分配资金
vm.deal(loser, value1);
vm.deal(winner, value2);
vm.prank(loser);
auction.bid{value: value1}(keccak256(abi.encodePacked(value1, false, "loser")));
vm.prank(winner);
auction.bid{value: value2}(keccak256(abi.encodePacked(value2, false, "winner")));
// Reveal period
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Reveal
vm.prank(loser);
auction.reveal(_toArray(value1), _toArray(false), _toArray(keccak256("loser")));
vm.prank(winner);
auction.reveal(_toArray(value2), _toArray(false), _toArray(keccak256("winner")));
// 提取资金
vm.prank(loser);
auction.withdraw();
// 验证结果
assertEq(
loser.balance,
initialLoserTotalBalance, // 初始余额 + 失败者提取资金
"Loser should receive full refund"
);
// 验证胜者无法提取资金
uint256 initialWinnerBalance = winner.balance;
vm.prank(winner);
auction.withdraw();
assertEq(winner.balance, initialWinnerBalance, "Winner should not withdraw before auction end");
assertEq(initialWinnerTotalBalance, winner.balance, "Winner should not withdraw before auction end");
}
function testWithdrawNothing() public {
uint256 initialBalance = bidder1.balance;
vm.prank(bidder1);
auction.withdraw(); // Attempt to withdraw with no pending funds
assertEq(bidder1.balance, initialBalance); // Balance unchanged
assertEq(auction.pendingReturns(bidder1), 0); // No pending returns
}
// 重复结束拍卖测试
function test_RevertWhen_AuctionEndTwice2() public {
// 完整完成拍卖
_completeAuction(bidder1, 1 ether);
vm.warp(auction.biddingEnd() + auction.revealEnd() + 1);
auction.auctionEnd(); // 第一次成功结束
vm.expectRevert(abi.encodeWithSelector(BlindAuction.AuctionEndAlreadyCalled.selector));
// 第二次预期失败
auction.auctionEnd();
}
// ================ 时间和状态测试 ================ //
function testEarlyBidAttempt() public {
// 使用合约的绝对结束时间计算
uint256 biddingEnd = auction.biddingEnd();
vm.warp(biddingEnd + 1); // 精确设置时间
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, "late"));
vm.deal(bidder1, 1 ether);
vm.expectRevert(abi.encodeWithSelector(BlindAuction.TooLate.selector, auction.biddingEnd()));
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
}
function testEarlyRevealAttempt() public {
// Try to reveal before bidding ends
vm.expectRevert(abi.encodeWithSelector(BlindAuction.TooEarly.selector, auction.biddingEnd()));
vm.prank(bidder1);
auction.reveal(new uint256[](0), new bool[](0), new bytes32[](0));
}
function testEarlyAuctionEndAttempt() public {
// Try to end auction before reveal ends
vm.warp(block.timestamp + BIDDING_TIME + 1);
vm.expectRevert(abi.encodeWithSelector(BlindAuction.TooEarly.selector, auction.revealEnd()));
auction.auctionEnd();
}
function testDoubleAuctionEndAttempt() public {
// Complete auction
_completeAuction(bidder1, 1 ether);
vm.warp(block.timestamp + REVEAL_TIME + 1);
auction.auctionEnd(); // Valid end
// Attempt to end again
vm.expectRevert(abi.encodeWithSelector(BlindAuction.AuctionEndAlreadyCalled.selector));
auction.auctionEnd();
}
function testDoubleAuctionEndAttempt2() public {
// Complete auction
_completeAuction(bidder1, 1 ether);
vm.warp(block.timestamp + REVEAL_TIME + 1);
auction.auctionEnd(); // Valid end
// Attempt to end again
vm.expectRevert(abi.encodeWithSelector(BlindAuction.AuctionEndAlreadyCalled.selector));
auction.auctionEnd();
}
function testZeroValueBid() public {
vm.expectRevert(abi.encodeWithSelector(BlindAuction.InvalidBidReveal.selector));
vm.prank(bidder1);
auction.bid{value: 0}(keccak256("zero"));
}
function testInvalidConstructorParams() public {
vm.expectRevert(abi.encodeWithSelector(BlindAuction.InvalidBidReveal.selector));
new BlindAuction(0, 0, payable(address(0)));
}
function testInvalidTimeParams() public {
new BlindAuction(0, REVEAL_TIME, beneficiary); // biddingTime 为 0
}
function testRevealWithMismatchedArrays() public {
// Submit a bid
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, secret));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
// Move to reveal phase
vm.warp(block.timestamp + BIDDING_TIME + 1);
// Provide mismatched arrays
uint256[] memory values = new uint256[](1);
bool[] memory fakes = new bool[](2); // Length mismatch
bytes32[] memory secrets = new bytes32[](1);
values[0] = 1 ether;
fakes[0] = false;
fakes[1] = true;
secrets[0] = secret;
vm.prank(bidder1);
vm.expectRevert("Fakes length mismatch"); // Match contract's error
auction.reveal(values, fakes, secrets);
}
function testAuctionEndWithUnLockedFunds() public {
// Submit a bid
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, secret));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
// Move to reveal phase and reveal the bid
vm.warp(block.timestamp + BIDDING_TIME + 1);
vm.prank(bidder1);
auction.reveal(_toArray(1 ether), _toArray(false), _toArray(secret));
// Correctly set lockedFunds[bidder1] to 0
bytes32 slot = keccak256(abi.encode(bidder1, uint256(7))); // Slot 7 for lockedFunds
vm.store(address(auction), slot, bytes32(0));
// Move past reveal phase to auction end phase
vm.warp(auction.revealEnd() + 1);
vm.expectRevert(abi.encodeWithSelector(BlindAuction.FundsNotLockedCorrectly.selector));
auction.auctionEnd();
}
function testAuctionEndWithUnLockedFunds2() public {
// Submit a bid
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, secret));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
// Move to reveal phase and reveal the bid
vm.warp(block.timestamp + BIDDING_TIME + 1);
vm.prank(bidder1);
auction.reveal(_toArray(1 ether), _toArray(false), _toArray(secret));
// 验证锁定资金是否正确设置
assertEq(auction.lockedFunds(bidder1), 1 ether, "Funds should be locked initially");
// 手动修改锁定资金为0(模拟未锁定状态)
bytes32 slot = keccak256(abi.encode(bidder1, uint256(7))); // Slot 7 for lockedFunds
vm.store(address(auction), slot, bytes32(0));
assertEq(auction.lockedFunds(bidder1), 0, "Funds should be unlocked after manipulation");
// Move past reveal phase to auction end phase
vm.warp(auction.revealEnd() + 1);
// 修改为检查自定义错误
vm.expectRevert(abi.encodeWithSelector(BlindAuction.FundsNotLockedCorrectly.selector));
auction.auctionEnd();
}
// 测试 reveal 函数中 bidToCheck.blindedBid 不匹配的情况
function testRevealWithNonMatchingBid() public {
bytes32 secret = keccak256("secret");
bytes32 blindedBid = keccak256(abi.encodePacked(uint256(1 ether), false, secret));
vm.deal(bidder1, 1 ether);
vm.prank(bidder1);
auction.bid{value: 1 ether}(blindedBid);
// 进入揭示阶段
vm.warp(block.timestamp + BIDDING_TIME + 1);
// 使用不匹配的秘密揭示
bytes32 wrongSecret = keccak256("wrong");
vm.prank(bidder1);
auction.reveal(_toArray(1 ether), _toArray(false), _toArray(wrongSecret));
// 验证出价没有被接受
assertEq(auction.highestBid(), 0);
assertEq(auction.highestBidder(), address(0));
}
// 测试最大可能的出价
function testMaximumBid() public {
uint256 maxBid = type(uint256).max;
bytes32 secret = keccak256("max");
bytes32 blindedBid = keccak256(abi.encodePacked(maxBid, false, secret));
vm.deal(bidder1, maxBid);
vm.prank(bidder1);
auction.bid{value: maxBid}(blindedBid);
// 进入揭示阶段
vm.warp(block.timestamp + BIDDING_TIME + 1);
// 揭示出价
vm.prank(bidder1);
auction.reveal(_toArray(maxBid), _toArray(false), _toArray(secret));...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!