Skip to content

Commit 938913a

Browse files
committed
Add vyper ci test
1 parent 8fdc90e commit 938913a

File tree

3 files changed

+193
-1
lines changed

3 files changed

+193
-1
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
fail-fast: false
2323
matrix:
2424
os: ["ubuntu-latest", "windows-2022"]
25-
type: ["brownie", "buidler", "dapp", "embark", "hardhat", "solc", "truffle", "waffle", "foundry", "standard"]
25+
type: ["brownie", "buidler", "dapp", "embark", "hardhat", "solc", "truffle", "waffle", "foundry", "standard", "vyper"]
2626
exclude:
2727
# Currently broken, tries to pull git:// which is blocked by GH
2828
- type: embark

scripts/ci_test_vyper.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
3+
### Test vyper integration
4+
5+
pip install vyper
6+
7+
echo "Testing vyper integration of $(realpath "$(which crytic-compile)")"
8+
9+
cd tests/vyper || exit 255
10+
11+
if ! crytic-compile auction.vy
12+
then echo "vyper test failed" && exit 255
13+
else echo "vyper test passed" && exit 0
14+
fi

tests/vyper/auction.vy

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

0 commit comments

Comments
 (0)