Blind Auction¶
Warning
This is example code for learning purposes. Do not use in production without thorough review and testing.
Before we dive into our other examples, let’s briefly explore another type of auction that you can build with Vyper. Similar to blind auction examples in Solidity, this contract allows for an auction where there is no time pressure towards the end of the bidding period.
1#pragma version >0.3.10
2
3# Blind Auction. Adapted to Vyper from [Solidity by Example](https://github.com/ethereum/solidity/blob/develop/docs/solidity-by-example.rst#blind-auction-1)
4
5struct Bid:
6 blindedBid: bytes32
7 deposit: uint256
8
9# Note: because Vyper does not allow for dynamic arrays, we have limited the
10# number of bids that can be placed by one address to 128 in this example
11MAX_BIDS: constant(int128) = 128
12
13# Event for logging that auction has ended
14event AuctionEnded:
15 highestBidder: address
16 highestBid: uint256
17
18# Auction parameters
19beneficiary: public(address)
20biddingEnd: public(uint256)
21revealEnd: public(uint256)
22
23# Set to true at the end of auction, disallowing any new bids
24ended: public(bool)
25
26# Final auction state
27highestBid: public(uint256)
28highestBidder: public(address)
29
30# State of the bids
31bids: HashMap[address, Bid[128]]
32bidCounts: HashMap[address, int128]
33
34# Allowed withdrawals of previous bids
35pendingReturns: HashMap[address, uint256]
36
37
38# Create a blinded auction with `_biddingTime` seconds bidding time and
39# `_revealTime` seconds reveal time on behalf of the beneficiary address
40# `_beneficiary`.
41@deploy
42def __init__(_beneficiary: address, _biddingTime: uint256, _revealTime: uint256):
43 self.beneficiary = _beneficiary
44 self.biddingEnd = block.timestamp + _biddingTime
45 self.revealEnd = self.biddingEnd + _revealTime
46
47
48# Place a blinded bid with:
49#
50# _blindedBid = keccak256(concat(
51# convert(value, bytes32),
52# convert(fake, bytes32),
53# secret)
54# )
55#
56# The sent ether is only refunded if the bid is correctly revealed in the
57# revealing phase. The bid is valid if the ether sent together with the bid is
58# at least "value" and "fake" is not true. Setting "fake" to true and sending
59# not the exact amount are ways to hide the real bid but still make the
60# required deposit. The same address can place multiple bids.
61@external
62@payable
63def bid(_blindedBid: bytes32):
64 # Check if bidding period is still open
65 assert block.timestamp < self.biddingEnd
66
67 # Check that payer hasn't already placed maximum number of bids
68 numBids: int128 = self.bidCounts[msg.sender]
69 assert numBids < MAX_BIDS
70
71 # Add bid to mapping of all bids
72 self.bids[msg.sender][numBids] = Bid(
73 blindedBid=_blindedBid,
74 deposit=msg.value
75 )
76 self.bidCounts[msg.sender] += 1
77
78
79# Returns a boolean value, `True` if bid placed successfully, `False` otherwise.
80@internal
81def placeBid(bidder: address, _value: uint256) -> bool:
82 # If bid is less than highest bid, bid fails
83 if (_value <= self.highestBid):
84 return False
85
86 # Refund the previously highest bidder
87 if (self.highestBidder != empty(address)):
88 self.pendingReturns[self.highestBidder] += self.highestBid
89
90 # Place bid successfully and update auction state
91 self.highestBid = _value
92 self.highestBidder = bidder
93
94 return True
95
96
97# Reveal your blinded bids. You will get a refund for all correctly blinded
98# invalid bids and for all bids except for the totally highest.
99@external
100def reveal(_numBids: int128, _values: uint256[128], _fakes: bool[128], _secrets: bytes32[128]):
101 # Check that bidding period is over
102 assert block.timestamp > self.biddingEnd
103
104 # Check that reveal end has not passed
105 assert block.timestamp < self.revealEnd
106
107 # Check that number of bids being revealed matches log for sender
108 assert _numBids == self.bidCounts[msg.sender]
109
110 # Calculate refund for sender
111 refund: uint256 = 0
112 for i: int128 in range(MAX_BIDS):
113 # Note that loop may break sooner than 128 iterations if i >= _numBids
114 if (i >= _numBids):
115 break
116
117 # Get bid to check
118 bidToCheck: Bid = (self.bids[msg.sender])[i]
119
120 # Check against encoded packet
121 value: uint256 = _values[i]
122 fake: bool = _fakes[i]
123 secret: bytes32 = _secrets[i]
124 blindedBid: bytes32 = keccak256(concat(
125 convert(value, bytes32),
126 convert(fake, bytes32),
127 secret
128 ))
129
130 # Bid was not actually revealed
131 # Do not refund deposit
132 assert blindedBid == bidToCheck.blindedBid
133
134 # Add deposit to refund if bid was indeed revealed
135 refund += bidToCheck.deposit
136 if (not fake and bidToCheck.deposit >= value):
137 if (self.placeBid(msg.sender, value)):
138 refund -= value
139
140 # Make it impossible for the sender to re-claim the same deposit
141 zeroBytes32: bytes32 = empty(bytes32)
142 bidToCheck.blindedBid = zeroBytes32
143
144 # Send refund if non-zero
145 if (refund != 0):
146 send(msg.sender, refund)
147
148
149# Withdraw a bid that was overbid.
150@external
151def withdraw():
152 # Check that there is an allowed pending return.
153 pendingAmount: uint256 = self.pendingReturns[msg.sender]
154 if (pendingAmount > 0):
155 # If so, set pending returns to zero to prevent recipient from calling
156 # this function again as part of the receiving call before `transfer`
157 # returns (see the remark above about conditions -> effects ->
158 # interaction).
159 self.pendingReturns[msg.sender] = 0
160
161 # Then send return
162 send(msg.sender, pendingAmount)
163
164
165# End the auction and send the highest bid to the beneficiary.
166@external
167def auctionEnd():
168 # Check that reveal end has passed
169 assert block.timestamp > self.revealEnd
170
171 # Check that auction has not already been marked as ended
172 assert not self.ended
173
174 # Log auction ending and set flag
175 log AuctionEnded(highestBidder=self.highestBidder, highestBid=self.highestBid)
176 self.ended = True
177
178 # Transfer funds to beneficiary
179 send(self.beneficiary, self.highestBid)
While this blind auction is almost functionally identical to the blind auction implemented in Solidity, the differences in their implementations help illustrate the differences between Solidity and Vyper.
9# Note: because Vyper does not allow for dynamic arrays, we have limited the
10# number of bids that can be placed by one address to 128 in this example
11MAX_BIDS: constant(int128) = 128
One difference is that in this example, we use a fixed-size array, limiting the number of bids that can be placed by one address to 128 in this example. Bidders who want to make more than this maximum number of bids would need to do so from multiple addresses.