iT邦幫忙

2024 iThome 鐵人賽

DAY 28
0
Modern Web

web3 短篇集系列 第 28

案例研究:Create2 Vickrey Auction

  • 分享至 

  • xImage
  •  

create2-vickrey-contracts 是一個黑客松競賽的作品,它的特色是使用 create2 來達到匿名競標的效果。

Vickrey Auction 是彌封次價拍賣的別稱,競標者將自己的出價放進信封袋不讓別人看到,拍賣的贏家只需要支付第二高價。為什麼是第二高價?因為最高出價者其實只需出價比第二高價者多一元,同樣能競標到商品。

本篇的先備知識:

在之前介紹過的 Blind Auction 使用 Commit-Reveal Scheme 來隱藏競標者的出價,但競標者需要夠過超額抵押來隱藏自己的真實出價,此外一場拍賣的參與者地址與總量是公開的。

那如何用 create2 做到匿名競標?當用戶要競標時,使用 create2 獲得一個指定地址,然後將 ETH 打進去,此時在區塊鏈上這是一筆「打錢進新地址」的交易,它在某種程度上可以藏樹於林,沒有人知道這筆轉帳其實正在參與拍賣的競標。

直到拍賣的 reveal 階段,用戶呼叫合約上的 reveal(),合約會部署你指定的 create2 地址,此時這場拍賣的參與者才顯露出來,合約一經部署就會將裡面的 ETH 轉到拍賣合約,成為用戶的出價,並記錄當前的最高與次高出價,等待所有人都 reveal 後完成拍賣。

競標者可否選擇不 reveal?不行,因為 create2 的地址來自於 contract creator,也就是說當你打錢進 create2 地址時(決定參與競標時),這個 create2 合約就必須由拍賣合約來部署,競標者無法自己部署把錢領回。

create2 address = keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]

有一個問題是,當用戶看到別人 reveal 的出價比自己高,於是在自己 reveal 前又送一點 ETH 到 create2 地址來增加自己的出價,這樣顯然不公平。

解決的方法是使用昨天介紹的 eth_getProof,首先拍賣合約會在 reveal 階段開始時,將當下的 blockhash 存起來。用戶要 reveal 時,必須提供自己在這個 blockhash 時的餘額證明,因此在拍賣合約部署 create2 合約時,會先檢查 proof 取得用戶在該 blockhash 的歷史餘額,於是可確認 balance snapshot,這才是用戶真正的出價,若用戶在 reveal 階段主委加碼出價,使得 create2 內的 ETH 比 balance snapshot 來多,就把多出來的部分退還給用戶。

總地來說,拍賣合約上的 reveal() 首先驗證用戶的歷史餘額,然後再部署 create2 合約並將裡面的錢轉到拍賣合約,最後儲存合約當前的最高價、次高價與最高出價人的地址。

以下是程式碼片段:

function reveal(
    address _bidder,
    uint256 _bid,
    bytes32 _subSalt,
    uint256 _balAtSnapshot,
    EthereumDecoder.BlockHeader memory _header,
    MPT.MerkleProof memory _accountDataProof
) external returns (address bidAddr) {
    uint256 totalBid;
    {
        if (revealStartBlock + 7200 < block.number) revert RevealOver();
        bytes32 storedBlockHashCached = storedBlockHash;
        if (storedBlockHashCached == bytes32(0)) revert NotYetReveal();
        (bytes32 salt, address depositAddr) = getBidDepositAddr(
            _bidder,
            _bid,
            _subSalt
        );

        if (
            !_verifyProof(
                _header,
                _accountDataProof,
                _balAtSnapshot,
                depositAddr,
                storedBlockHashCached
            )
        ) revert InvalidProof();

        uint256 balBefore = address(this).balance;
        assembly {
            mstore(0x00, BID_EXTRACTOR_CODE)
            bidAddr := create2(
                0,
                BID_EXTRACTOR_CODE_OFFSET,
                BID_EXTRACTOR_CODE_SIZE,
                salt
            )
        }
        totalBid = address(this).balance - balBefore;
    }

    uint256 actualBid = min(_bid, _balAtSnapshot);
    uint256 bidderRefund = totalBid - actualBid;

    uint128 topBidCached = topBid;
    uint128 sndBidCached = sndBid;
    address topBidderCached = topBidder;
    if (actualBid > topBidCached) {
        _asyncSend(topBidderCached, topBidCached);
        topBidder = topBidderCached = _bidder;
        sndBid = sndBidCached = uint128(topBidCached);
        topBid = topBidCached = uint128(actualBid);
    } else {
        if (actualBid > sndBid) sndBid = sndBidCached = uint128(actualBid);
        bidderRefund += actualBid;
    }

    _asyncSend(_bidder, bidderRefund);

    emit BidRevealed(
        topBidderCached,
        _bidder,
        topBidCached,
        sndBidCached,
        actualBid
    );
}

Reference


上一篇
使用 eth_getProof 證明帳戶的歷史餘額
下一篇
Solidity 初學者避坑指南
系列文
web3 短篇集30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言