First verified Ethereum Smart Contract written in Vyper on etherscan

Vyper ethereum

First verified Ethereum smart contract written in Vyper is now live on etherscan.

Here is the code for the curious ones.

# Blind Auction # Adapted to Vyper from [Solidity by Example](

struct Bid:
  blindedBid: bytes32
  deposit: wei_value

# Note: because Vyper does not allow for dynamic arrays, we have limited the
# number of bids that can be placed by one address to 128 in this example
MAX_BIDS: constant(int128) = 128

# Event for logging that auction has ended
AuctionEnded: event({_highestBidder: address, _highestBid: wei_value})

# Auction parameters
beneficiary: public(address)
biddingEnd: public(timestamp)
revealEnd: public(timestamp)

# Set to true at the end of auction, disallowing any new bids
ended: public(bool)

# Final auction state
highestBid: public(wei_value)
highestBidder: public(address)

# State of the bids
bids: map(address, Bid[128])
bidCounts: map(address, int128)

# Allowed withdrawals of previous bids
pendingReturns: map(address, wei_value)

# Create a blinded auction with `_biddingTime` seconds bidding time and
# `_revealTime` seconds reveal time on behalf of the beneficiary address
# `_beneficiary`.
def __init__(_beneficiary: address, _biddingTime: timedelta, _revealTime: timedelta):
    self.beneficiary = _beneficiary
    self.biddingEnd = block.timestamp + _biddingTime
    self.revealEnd = self.biddingEnd + _revealTime

# Place a blinded bid with:
# _blindedBid = sha3(concat(
#       convert(value, bytes32),
#       convert(fake, bytes32),
#       secret)
# )
# The sent ether is only refunded if the bid is correctly revealed in the
# revealing phase. The bid is valid if the ether sent together with the bid is
# at least "value" and "fake" is not true. Setting "fake" to true and sending
# not the exact amount are ways to hide the real bid but still make the
# required deposit. The same address can place multiple bids.
def bid(_blindedBid: bytes32):
    # Check if bidding period is still open
    assert block.timestamp < self.biddingEnd

    # Check that payer hasn't already placed maximum number of bids
    numBids: int128 = self.bidCounts[msg.sender]
    assert numBids < MAX_BIDS

    # Add bid to mapping of all bids
    self.bids[msg.sender][numBids] = Bid({
        blindedBid: _blindedBid,
        deposit: msg.value
    self.bidCounts[msg.sender] += 1

# Returns a boolean value, `True` if bid placed successfully, `False` otherwise.
def placeBid(bidder: address, value: wei_value) -> bool:
    # If bid is less than highest bid, bid fails
    if (value <= self.highestBid):
        return False

    # Refund the previously highest bidder
    if (self.highestBidder != ZERO_ADDRESS):
        self.pendingReturns[self.highestBidder] += self.highestBid

    # Place bid successfully and update auction state
    self.highestBid = value
    self.highestBidder = bidder

    return True

# Reveal your blinded bids. You will get a refund for all correctly blinded
# invalid bids and for all bids except for the totally highest.
def reveal(_numBids: int128, _values: wei_value[128], _fakes: bool[128], _secrets: bytes32[128]):
    # Check that bidding period is over
    assert block.timestamp > self.biddingEnd

    # Check that reveal end has not passed
    assert block.timestamp < self.revealEnd

    # Check that number of bids being revealed matches log for sender
    assert _numBids == self.bidCounts[msg.sender]

    # Calculate refund for sender
    refund: wei_value
    for i in range(MAX_BIDS):
        # Note that loop may break sooner than 128 iterations if i >= _numBids
        if (i >= _numBids):

        # Get bid to check
        bidToCheck: Bid = (self.bids[msg.sender])[i]

        # Check against encoded packet
        value: wei_value = _values[i]
        fake: bool = _fakes[i]
        secret: bytes32 = _secrets[i]
        blindedBid: bytes32 = sha3(concat(
            convert(value, bytes32),
            convert(fake, bytes32),

        # Bid was not actually revealed
        # Do not refund deposit
        if (blindedBid != bidToCheck.blindedBid):
            assert 1 == 0

        # Add deposit to refund if bid was indeed revealed
        refund += bidToCheck.deposit
        if (not fake and bidToCheck.deposit >= value):
            if (self.placeBid(msg.sender, value)):
                refund -= value

        # Make it impossible for the sender to re-claim the same deposit
        zeroBytes32: bytes32
        bidToCheck.blindedBid = zeroBytes32

    # Send refund if non-zero
    if (refund != 0):
        send(msg.sender, refund)

# Withdraw a bid that was overbid.
def withdraw():
    # Check that there is an allowed pending return.
    pendingAmount: wei_value = self.pendingReturns[msg.sender]
    if (pendingAmount > 0):
        # If so, set pending returns to zero to prevent recipient from calling
        # this function again as part of the receiving call before `transfer`
        # returns (see the remark above about conditions -> effects ->
        # interaction).
        self.pendingReturns[msg.sender] = 0

        # Then send return
        send(msg.sender, pendingAmount)

# End the auction and send the highest bid to the beneficiary.
def auctionEnd():
    # Check that reveal end has passed
    assert block.timestamp > self.revealEnd

    # Check that auction has not already been marked as ended
    assert not self.ended

    # Log auction ending and set flag
    log.AuctionEnded(self.highestBidder, self.highestBid)
    self.ended = True

    # Transfer funds to beneficiary
    send(self.beneficiary, self.highestBid)