目录
[4、 错误处理](#4、 错误处理)
[5、 修饰符](#5、 修饰符)
[8、 披露出价](#8、 披露出价)
[9、 处理最高出价](#9、 处理最高出价)
[10、 提款功能](#10、 提款功能)
前言
盲拍合约是一种创新的拍卖机制,旨在通过保护买家的隐私和增强出价的公平性来提高交易效率。在这种合约中,参与者在未透露具体出价的情况下,基于对商品的描述或估值进行匿名竞标。盲拍合约不仅适用于艺术品和收藏品的拍卖,还能广泛应用于房地产、商品销售等多个领域,从而确保每位参与者都有平等的机会,降低了价格操纵的风险。
可访问文档:https://learnblockchain.cn/docs/solidity/0.5.9/examples/blind-auction.html#simple-auction
一、盲拍合约是什么?
盲拍合约是一种在拍卖或交易中使用的合同形式,特别是在拍卖艺术品、收藏品或其他资产时,买家在未实际查看商品的情况下提交竞标或购买意向。这种合约的特点在于,买家对拍卖品的具体情况(如质量、状态等)没有事先了解,而是基于对商品的描述或估值来做出购买决定。
二、盲拍合约工作原理
1、合约创建与初始化
合约部署 : 创建盲拍合约时,设置拍卖的受益人地址、出价时间和披露时间。这些参数定义了拍卖的基本规则。
2、用户出价(Bid)
匿名出价 : 用户通过调用 bid
函数提交出价。用户提供三个参数:出价金额(value
)、真假标记(fake
)和秘密(secret
)。
加密出价 : 合约使用 keccak256
哈希函数计算出一个加密出价(blindedBid
),该出价结合了用户的真实出价和其他参数。这确保了在拍卖期间用户的出价保持匿名。
存储出价 : 合约将加密出价和用户存款存储在 bids
映射中。
3、出价结束
结束出价 : 在设置的出价时间结束后,用户无法再提交新的出价。这是通过 onlyBefore
修饰符控制的。
4、披露出价(Reveal)
出价披露 : 在出价结束后 ,用户可以调用 reveal
函数,提交真实的出价信息,包括真实值、真假标记和秘密。
验证出价: 合约验证用户披露的出价是否与之前提交的加密出价匹配。如果匹配且不是假出价,合约会更新最高出价并可能会退款。
退款机制: 对于不符合条件的出价,合约会保留存款。用户可以在出价被超越后提取退款。
5、处理最高出价
更新最高出价 : 当用户披露有效的真实出价时,合约会检查是否高于当前最高出价。如果是,合约会更新最高出价和最高出价者的地址,并将之前的最高出价退还给原持有者。
6、结束拍卖
拍卖结束 : 在披露时间结束后,合约通过 auctionEnd
函数结束拍卖。合约会将最高出价发送给受益人,并触发相应的事件记录结果。
7、退款与提款
提款功能 : 用户可以通过调用 withdraw
函数提取未使用的存款,确保合约能够安全处理资金。
三、解析盲拍合约代码
1、数据结构
Bid
: 定义了一个结构体,包含两个字段:blindedBid
: 存储用户加密的出价(bytes32
类型)。deposit
: 用户为出价提供的存款(uint
类型)。
2、合约状态变量
beneficiary
: 合约的受益人地址,拍卖结束后,最高出价将转移到该地址。biddingEnd
: 表示出价结束的时间戳。revealEnd
: 表示出价披露结束的时间戳。ended
: 布尔值,指示拍卖是否已结束。bids
: 映射,每个用户地址对应一个出价数组,存储用户的所有出价。highestBidder
: 当前最高出价的用户地址。highestBid
: 当前最高出价的金额。pendingReturns
: 映射,存储待退还的金额。
3、事件
AuctionEnded
: 当拍卖结束时触发的事件,记录赢家和最高出价。
4、 错误处理
定义了一些错误,描述可能的失败情况:
TooEarly
: 调用函数过早。
TooLate
: 调用函数过晚。
AuctionEndAlreadyCalled
: 拍卖结束函数已被调用。
5、 修饰符
onlyBefore(uint time)
: 用于限制函数只能在指定时间之前调用。
onlyAfter(uint time)
: 用于限制函数只能在指定时间之后调用。
6、构造函数
constructor
: 在合约部署时被调用,初始化受益人地址和拍卖的时间限制。
7、出价功能
bid(uint value, bool fake, bytes32 secret)
:
用户通过此函数提交出价。出价通过
keccak256
生成加密值(blindedBid
),将其与存款一起存储。
fake
参数用于指示出价是否为假,允许用户隐藏真实出价。
8、 披露出价
reveal(...)
:
用户在出价披露阶段调用该函数,提交之前的出价信息(真实值、真假标记和秘密)。
检查提交的出价是否与之前的加密出价匹配。如果匹配且不是假出价,可能会更新最高出价并退款。
未能正确披露的出价将不会退还存款。
9、 处理最高出价
placeBid(address bidder, uint value)
:
内部函数,用于检查新出价是否高于当前最高出价。
如果新出价更高,更新最高出价并处理之前的最高出价的退款。
10、 提款功能
withdraw()
:
允许用户提取之前被超越的出价。
在提款前,将待退还金额设为零,以防止重入攻击。
11、结束拍卖
auctionEnd()
:
在出价披露结束后调用,结束拍卖并将最高出价发送给受益人。
触发
AuctionEnded
事件,记录赢家和最高出价。
四、盲拍合约用途
盲拍合约的用途广泛,主要适用于需要公平竞争和保护隐私的场景。以下是一些具体的应用领域:
-
艺术品拍卖: 在艺术品市场中,盲拍合约可以确保买家在出价时不受到其他买家影响,从而更真实地反映出对艺术品的价值评估。
-
房地产拍卖: 房地产拍卖中,买家可以在不透露出价的情况下参与竞标,避免价格上涨的心理战,促使更公平的交易。
-
商品销售: 在线市场和电商平台可以使用盲拍机制来售卖稀缺商品,确保所有参与者都有平等机会获取商品。
-
融资和投资: 在众筹或投资项目中,盲拍合约可以用来收集投资者的出价,确保所有出价在披露前保持匿名,促进公平竞争。
-
资源分配: 在需要分配有限资源的情况下(如频谱拍卖),盲拍合约能够确保各方以匿名方式出价,最大化资源的有效利用。
-
游戏和竞赛: 在游戏或竞赛中,盲拍合约可以用于奖励机制,确保参赛者在竞标奖励时不会受到其他参赛者的影响。
-
政府拍卖: 政府在拍卖公共资产(如土地、许可证)时,可以使用盲拍合约来确保出价过程的公正性和透明度。
通过在这些场景中使用盲拍合约,可以提高交易的公平性、透明度和效率,同时保护参与者的隐私,减少潜在的欺诈行为。
五、完整代码示例
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.2;
contract BlindAuction {
struct Bid {
bytes32 blindedBid;
uint deposit;
}
address payable public beneficiary;
uint public biddingEnd;
uint public revealEnd;
bool public ended;
mapping(address => Bid[]) public bids;
address public highestBidder;
uint public highestBid;
// 可以取回的之前的出价
mapping(address => uint) public pendingReturns;
event AuctionEnded(address winner, uint highestBid);
// Errors that describe failures.
/// The function has been called too early.
/// Try again at `time`.
error TooEarly(uint time);
/// The function has been called too late.
/// It cannot be called after `time`.
error TooLate(uint time);
/// The function auctionEnd has already been called.
error AuctionEndAlreadyCalled();
/// 使用 modifier 可以更便捷的校验函数的入参。
/// `onlyBefore` 会被用于后面的 `bid` 函数:
/// 新的函数体是由 modifier 本身的函数体,并用原函数体替换 `_;` 语句来组成的。
modifier onlyBefore(uint time) {
if (block.timestamp >= time) revert TooLate(time);
_;
}
modifier onlyAfter(uint time) {
if (block.timestamp <= time) revert TooEarly(time);
_;
}
constructor(
uint biddingTime,
uint revealTime,
address payable beneficiaryAddress
) {
beneficiary = beneficiaryAddress;
biddingEnd = block.timestamp + biddingTime;
revealEnd = biddingEnd + revealTime;
}
// blindedBid1=..keccak256(abi.encodePacked(2, true, bytes32("sdfadf")));
/// 可以通过 `blindedBid` = keccak256(value, fake, secret)
/// 设置一个秘密竞拍。
/// 只有在出价披露阶段被正确披露,已发送的以太币才会被退还。
/// 如果与出价一起发送的以太币至少为 "value" 且 "fake" 不为真,则出价有效。
/// 将 "fake" 设置为 true ,然后发送满足订金金额但又不与出价相同的金额是隐藏实际出价的方法。
/// 同一个地址可以放置多个出价。
/*
function bid(bytes32 blindedBid)
external
payable
onlyBefore(biddingEnd)
{
bids[msg.sender].push(Bid({
blindedBid: blindedBid,
deposit: msg.value
}));
}
*/
function bid(uint value, bool fake, bytes32 secret)
external
payable
onlyBefore(biddingEnd)
{
// 计算 blindedBid 内部使用,仅供存储或其他用途
bytes32 blindedBid = keccak256(abi.encodePacked(value, fake, secret));
bids[msg.sender].push(Bid({
blindedBid: blindedBid,
deposit: msg.value
}));
}
/// 披露你的秘密竞拍出价。
/// 对于所有正确披露的无效出价以及除最高出价以外的所有出价,你都将获得退款。
function reveal(
uint[] calldata values,
bool[] calldata fakeFlags,
bytes32[] calldata secrets
)
external
onlyAfter(biddingEnd)
onlyBefore(revealEnd)
{
uint length = bids[msg.sender].length;
require(values.length == length, "Mismatched values length");
require(fakeFlags.length == length, "Mismatched fake flags length");
require(secrets.length == length, "Mismatched secrets length");
uint refund;
for (uint i = 0; i < length; i++) {
Bid storage bid = bids[msg.sender][i];
(uint value, bool fake, bytes32 secret) =
(values[i], fakeFlags[i], secrets[i]);
// 使用 abi.encodePacked 将多个参数打包成一个 bytes 类型
if (bid.blindedBid != keccak256(abi.encodePacked(value, fake, secret))) {
// 出价未能正确披露
// 不返还订金
// console.log("披露失败!");
continue;
}
refund += bid.deposit;
if (!fake && bid.deposit >= value) {
if (placeBid(msg.sender, value))
refund -= value;
}
// 使发送者不可能再次认领同一笔订金
bid.blindedBid = bytes32(0);
}
// 使用 call 代替 transfer,推荐在 Solidity 0.8.x 版本中使用
(bool success, ) = msg.sender.call{value: refund}("");
require(success, "Refund failed");
}
// 这是一个 "internal" 函数, 意味着它只能在本合约(或继承合约)内被调用
function placeBid(address bidder, uint value) internal
returns (bool success)
{
if (value <= highestBid) {
return false;
}
if (highestBidder != address(0)) {
// 返还之前的最高出价
pendingReturns[highestBidder] += highestBid;
}
highestBid = value;
highestBidder = bidder;
return true;
}
/// 取回出价(当该出价已被超越)
function withdraw() external {
uint amount = pendingReturns[msg.sender];
if (amount > 0) {
// 这里很重要,首先要设零值。
// 因为,作为接收调用的一部分,
// 接收者可以在 `call` 返回之前重新调用该函数。(可查看上面关于'条件 -> 影响 -> 交互'的标注)
pendingReturns[msg.sender] = 0;
// 使用 call 代替 transfer,推荐在 Solidity 0.8.x 版本中使用
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Withdrawal failed");
}
}
/// 结束拍卖,并把最高的出价发送给受益人
function auctionEnd()
external
onlyAfter(revealEnd)
{
if (ended) revert AuctionEndAlreadyCalled();
emit AuctionEnded(highestBidder, highestBid);
ended = true;
// 使用 call 代替 transfer,推荐在 Solidity 0.8.x 版本中使用
(bool success, ) = beneficiary.call{value: highestBid}("");
require(success, "Transfer to beneficiary failed");
}
}
总结
盲拍合约通过其独特的机制,实现了透明与隐私的平衡,推动了更公平的市场环境。合约的设计允许参与者在不泄露个人信息的情况下进行出价,有效防止了心理战和价格操控。在现代数字经济中,盲拍合约的应用潜力巨大,为各种交易场景提供了新方式,有助于提高交易的信任度和效率,促进了公平竞争。