-
Notifications
You must be signed in to change notification settings - Fork 187
/
uniswap.fe
338 lines (265 loc) · 12.5 KB
/
uniswap.fe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
contract ERC20:
pub def balanceOf(account: address) -> u256:
return 0
pub def transfer(recipient: address, amount: u256) -> bool:
return false
contract UniswapV2Pair:
# TODO: const support (https://github.com/ethereum/fe/issues/192)
# const MINIMUM_LIQUIDITY: u256 = 1000
balances: map<address, u256>
allowances: map<address, map<address, u256>>
total_supply: u256
nonces: map<address, u256>
factory: address
token0: address
token1: address
reserve0: u256
reserve1: u256
block_timestamp_last: u256
price0_cumulative_last: u256
price1_cumulative_last: u256
k_last: u256
event Approval:
idx owner: address
idx spender: address
value: u256
event Transfer:
idx from: address
idx to: address
value: u256
event Mint:
idx sender: address
amount0: u256
amount1: u256
event Burn:
idx sender: address
amount0: u256
amount1: u256
idx to: address
event Swap:
idx sender: address
amount0_in: u256
amount1_in: u256
amount0_out: u256
amount1_out: u256
idx to: address
event Sync:
reserve0: u256
reserve1: u256
pub def __init__():
self.factory = msg.sender
pub def factory() -> address:
return self.factory
pub def token0() -> address:
return self.token0
pub def token1() -> address:
return self.token1
def _mint(to: address, value: u256):
self.total_supply = self.total_supply + value
self.balances[to] = self.balances[to] + value
emit Transfer(address(0), to, value)
def _burn(from: address, value: u256):
self.balances[from] = self.balances[from] - value
self.total_supply = self.total_supply - value
emit Transfer(from, address(0), value)
def _approve(owner: address, spender: address, value: u256):
self.allowances[owner][spender] = value
emit Approval(owner, spender, value)
def _transfer(from: address, to: address, value: u256):
self.balances[from] = self.balances[from] - value
self.balances[to] = self.balances[to] + value
emit Transfer(from, to, value)
pub def approve(spender: address, value: u256) -> bool:
self._approve(msg.sender, spender, value)
return true
pub def transfer(to: address, value: u256) -> bool:
self._transfer(msg.sender, to, value)
return true
pub def transferFrom(from: address, to: address, value: u256) -> bool:
assert self.allowances[from][msg.sender] >= value
self.allowances[from][msg.sender] = self.allowances[from][msg.sender] - value
self._transfer(from, to, value)
return true
pub def balanceOf(account: address) -> u256:
return self.balances[account]
pub def get_reserves() -> (u256, u256, u256):
return (self.reserve0, self.reserve1, self.block_timestamp_last)
# called once by the factory at time of deployment
pub def initialize(token0: address, token1: address):
# TODO: we should not be ignoring the revert messages (https://github.com/ethereum/fe/issues/288)
assert msg.sender == self.factory, "UniswapV2: FORBIDDEN"
self.token0 = token0
self.token1 = token1
# update reserves and, on the first call per block, price accumulators
def _update(balance0: u256, balance1: u256, reserve0: u256, reserve1: u256):
# changed from u32s
block_timestamp: u256 = block.timestamp % 2**32
# TODO: reproduce desired overflow (https://github.com/ethereum/fe/issues/286)
time_elapsed: u256 = block_timestamp - self.block_timestamp_last # overflow is desired
if time_elapsed > 0 and reserve0 != 0 and reserve1 != 0:
# `*` never overflows, and + overflow is desired
self.price0_cumulative_last = self.price0_cumulative_last + (reserve1 / reserve0) * time_elapsed
self.price1_cumulative_last = self.price1_cumulative_last + (reserve0 / reserve1) * time_elapsed
self.reserve0 = balance0
self.reserve1 = balance1
self.block_timestamp_last = block_timestamp
emit Sync(self.reserve0, self.reserve1)
# if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k)
def _mint_fee(reserve0: u256, reserve1: u256) -> bool:
fee_to: address = UniswapV2Factory(self.factory).fee_to()
fee_on: bool = fee_to != address(0)
k_last: u256 = self.k_last # gas savings
if fee_on:
if k_last != 0:
root_k: u256 = self.sqrt(reserve0 * reserve1)
root_k_last: u256 = self.sqrt(k_last)
if root_k > root_k_last:
numerator: u256 = self.total_supply * root_k - root_k_last
denominator: u256 = root_k * 5 + root_k_last
liquidity: u256 = numerator / denominator
if liquidity > 0:
self._mint(fee_to, liquidity)
elif k_last != 0:
self.k_last = 0
return fee_on
# this low-level function should be called from a contract which performs important safety checks
pub def mint(to: address) -> u256:
MINIMUM_LIQUIDITY: u256 = 1000
reserve0: u256 = self.reserve0
reserve1: u256 = self.reserve1
balance0: u256 = ERC20(self.token0).balanceOf(self.address)
balance1: u256 = ERC20(self.token1).balanceOf(self.address)
amount0: u256 = balance0 - self.reserve0
amount1: u256 = balance1 - self.reserve1
fee_on: bool = self._mint_fee(reserve0, reserve1)
total_supply: u256 = self.total_supply # gas savings, must be defined here since totalSupply can update in _mintFee
liquidity: u256 = 0
if total_supply == 0:
liquidity = self.sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY
self._mint(address(0), MINIMUM_LIQUIDITY) # permanently lock the first MINIMUM_LIQUIDITY tokens
else:
liquidity = self.min((amount0 * total_supply) / reserve0, (amount1 * total_supply) / reserve1)
assert liquidity > 0, "UniswapV2: INSUFFICIENT_LIQUIDITY_MINTED"
self._mint(to, liquidity)
self._update(balance0, balance1, reserve0, reserve1)
if fee_on:
self.k_last = reserve0 * reserve1 # reserve0 and reserve1 are up-to-date
emit Mint(msg.sender, amount0, amount1)
return liquidity
# this low-level function should be called from a contract which performs important safety checks
pub def burn(to: address) -> (u256, u256):
reserve0: u256 = self.reserve0
reserve1: u256 = self.reserve1
token0: ERC20 = ERC20(self.token0)
token1: ERC20 = ERC20(self.token1)
balance0: u256 = token0.balanceOf(self.address)
balance1: u256 = token1.balanceOf(self.address)
liquidity: u256 = self.balances[self.address]
fee_on: bool = self._mint_fee(reserve0, reserve1)
total_supply: u256 = self.total_supply # gas savings, must be defined here since total_supply can update in _mintFee
amount0: u256 = (liquidity * balance0) / total_supply # using balances ensures pro-rata distribution
amount1: u256 = (liquidity * balance1) / total_supply # using balances ensures pro-rata distribution
assert amount0 > 0 and amount1 > 0, "UniswapV2: INSUFFICIENT_LIQUIDITY_BURNED"
self._burn(self.address, liquidity)
token0.transfer(to, amount0)
token1.transfer(to, amount1)
balance0 = token0.balanceOf(self.address)
balance1 = token1.balanceOf(self.address)
self._update(balance0, balance1, reserve0, reserve1)
if fee_on:
self.k_last = reserve0 * reserve1 # reserve0 and reserve1 are up-to-date
emit Burn(msg.sender, amount0, amount1, to)
return (amount0, amount1)
# this low-level function should be called from a contract which performs important safety checks
# TODO: add support for the bytes type (https://github.com/ethereum/fe/issues/280)
# pub def swap(amount0_out: u256, amount1_out: u256, to: address, data: bytes):
pub def swap(amount0_out: u256, amount1_out: u256, to: address):
assert amount0_out > 0 or amount1_out > 0, "UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT"
reserve0: u256 = self.reserve0
reserve1: u256 = self.reserve1
assert amount0_out < reserve0 and amount1_out < reserve1, "UniswapV2: INSUFFICIENT_LIQUIDITY"
token0: ERC20 = ERC20(self.token0)
token1: ERC20 = ERC20(self.token1)
# TODO: we should be using `token0.address` (https://github.com/ethereum/fe/issues/287)
assert to != address(token0) and to != address(token1), "UniswapV2: INVALID_TO"
if amount0_out > 0:
token0.transfer(to, amount0_out) # optimistically transfer tokens
if amount1_out > 0:
token1.transfer(to, amount1_out) # optimistically transfer tokens
# TODO: bytes support
# if data.length > 0:
# IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0_out, amount1_out, data)
balance0: u256 = token0.balanceOf(self.address)
balance1: u256 = token1.balanceOf(self.address)
amount0_in: u256 = balance0 - (reserve0 - amount0_out) if balance0 > reserve0 - amount0_out else 0
amount1_in: u256 = balance1 - (reserve1 - amount1_out) if balance1 > reserve1 - amount1_out else 0
assert amount0_in > 0 or amount1_in > 0, "UniswapV2: INSUFFICIENT_INPUT_AMOUNT"
balance0_adjusted: u256 = balance0 * 1000 - amount0_in * 3
balance1_adjusted: u256 = balance1 * 1000 - amount1_in * 3
assert balance0_adjusted * balance1_adjusted >= reserve0 * reserve1 * 1000000, "UniswapV2: K"
self._update(balance0, balance1, reserve0, reserve1)
emit Swap(msg.sender, amount0_in, amount1_in, amount0_out, amount1_out, to)
# force balances to match reserves
pub def skim(to: address):
token0: ERC20 = ERC20(self.token0) # gas savings
token1: ERC20 = ERC20(self.token1) # gas savings
token0.transfer(to, token0.balanceOf(self.address) - self.reserve0)
token1.transfer(to, token1.balanceOf(self.address) - self.reserve1)
# force reserves to match balances
pub def sync():
token0: ERC20 = ERC20(self.token0)
token1: ERC20 = ERC20(self.token1)
self._update(token0.balanceOf(self.address), token1.balanceOf(self.address), self.reserve0, self.reserve1)
def sqrt(val: u256) -> u256:
z: u256
if (val > 3):
z = val
x: u256 = val / 2 + 1
while (x < z):
z = x
x = (val / x + x) / 2
elif (val != 0):
z = 1
return z
def min(x: u256, y: u256) -> u256:
return x if x < y else y
contract UniswapV2Factory:
fee_to: address
fee_to_setter: address
pairs: map<address, map<address, address>>
all_pairs: address[100]
pair_counter: u256
event PairCreated:
idx token0: address
idx token1: address
pair: address
index: u256
pub def __init__(fee_to_setter: address):
self.fee_to_setter = fee_to_setter
pub def fee_to() -> address:
return self.fee_to
pub def fee_to_setter() -> address:
return self.fee_to_setter
pub def all_pairs_length() -> u256:
return self.pair_counter
pub def create_pair(token_a: address, token_b: address) -> address:
assert token_a != token_b, "UniswapV2: IDENTICAL_ADDRESSES"
token0: address = token_a if token_a < token_b else token_b
token1: address = token_a if token_a > token_b else token_b
assert token0 != address(0), "UniswapV2: ZERO_ADDRESS"
assert self.pairs[token0][token1] == address(0), "UniswapV2: PAIR_EXISTS"
salt: u256 = keccak256((token0, token1).abi_encode())
pair: UniswapV2Pair = UniswapV2Pair.create2(0, salt)
pair.initialize(token0, token1)
self.pairs[token0][token1] = address(pair)
self.pairs[token1][token0] = address(pair)
self.all_pairs[self.pair_counter] = address(pair)
self.pair_counter = self.pair_counter + 1
emit PairCreated(token0, token1, address(pair), self.pair_counter)
return address(pair)
pub def set_fee_to(fee_to: address):
assert msg.sender == self.fee_to_setter, "UniswapV2: FORBIDDEN"
self.fee_to = fee_to
pub def set_fee_to_setter(fee_to_setter: address):
assert msg.sender == fee_to_setter, "UniswapV2: FORBIDDEN"
self.fee_to_setter = fee_to_setter