探索Web3新速度:Sonic高性能Layer-1上的BlindAuction智能合约实践

探索Web3新速度:Sonic高性能Layer-1上的BlindAuction智能合约实践在Web3的浪潮中,Sonic以其超高吞吐量(400,000+TPS)和亚秒级确认速度,正在重新定义区块链的性能边界。作为一款高性能EVMLayer-1区块链,Sonic为去中心化金融(DeFi)提供了无

探索Web3新速度:Sonic高性能Layer-1上的BlindAuction智能合约实践

在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

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,这些功能增强了网络的去中心化和可持续性。

实操 BlindAuction 合约

第一步:创建项目并切换到项目目录

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

第四步:实现合约 BlindAuction.sol

// 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并附带资金押金。进入揭示阶段biddingEndrevealEnd),参与者调用reveal函数公开实际出价金额、验证码和真伪标志,合约校验哈希匹配后,若出价真实且押金充足,则激活placeBid更新最高出价(原最高出价者资金进入待退款pendingReturns)。拍卖结束后,通过auctionEnd将最高出价资金(需通过lockedFunds校验)转给受益人beneficiary,其他参与者可通过withdraw提取退款。关键特性包括:

  1. 隐私保护:投标阶段隐藏实际出价
  2. 资金安全:最高出价资金锁定机制
  3. 错误处理:自定义错误类型(如时间校验、资金验证)
  4. 阶段控制:修饰器(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 受益人: 获得最高出价金额
    合约->>竞标者: 退还落标者押金

三大核心阶段

1. 密封出价阶段

  • 竞标者提交 keccak256(真实出价+密码)
  • 所有出价完全匿名(连合约都看不到真实金额)

2. 揭示阶段

  • 竞标者提交真实出价和密码
  • 合约验证:keccak256(提交值) = 原始哈希
  • 暴露伪装出价(fake bids)直接退还押金

3. 结算阶段

  • 最高出价转账给受益人
  • 其他竞标者通过 withdraw() 取回押金
graph LR
    A[加密提交] --> B[哈希盲拍]
    C[多重验证] --> D[防作弊机制]
    E[资金安全] --> F[独立退款通道]

    B -.-> |任何人无法预知出价| G(公平竞拍)
    D -.-> |无效报价自动退还| H(防欺诈)
    F -.-> |失败竞价原路返还| I(资金安全)

💡 就像「暗拍」游戏:

  1. 所有人把出价密封在信封里提交
  2. 同时开封验证真实出价
  3. 只有最高出价付款,其他人拿回押金 完美解决传统拍卖的跟拍策略问题!

测试合约

第一步:编写测试文件 BlindAuction.t.sol


// 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));...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
寻月隐君
寻月隐君
0xE91e...6bE5
不要放弃,如果你喜欢这件事,就不要放弃。如果你不喜欢,那这也不好,因为一个人不应该做自己不喜欢的事。