Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit cd5d95e

Browse filesBrowse files
authored
fix: make fee a required argument for Uniswap V3 (#358)
* Add FeeTier * Add FeeTier tests * Update project version 0.7.0 -> 0.7.1 * Revert several unnecessary changes * Fix tests
1 parent ad8c8a1 commit cd5d95e
Copy full SHA for cd5d95e

File tree

Expand file treeCollapse file tree

9 files changed

+209
-82
lines changed
Filter options
Expand file treeCollapse file tree

9 files changed

+209
-82
lines changed

‎README.md

Copy file name to clipboardExpand all lines: README.md
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ Contributors also earn this beautiful [GitPOAP](https://gitpoap.notion.site/What
8080

8181
## Changelog
8282

83+
_0.7.1_
84+
85+
* Updated: Default fee is not applied when using Uniswap V3. Default fee for Uniswap V1 and V2 is still 0.3%.
86+
* Updated: `InvalidFeeTier` exception is raised when a tier is not supported by the protocol version. See all supported tiers in [`uniswap.fee.FeeTier`](./uniswap/fee.py#L12)
87+
88+
_0.7.0_
89+
90+
* incomplete changelog
91+
8392
_0.5.4_
8493

8594
* added use of gas estimation instead of a fixed gas limit (to support Arbitrum)

‎pyproject.toml

Copy file name to clipboardExpand all lines: pyproject.toml
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "uniswap-python"
3-
version = "0.7.0"
3+
version = "0.7.1"
44
description = "An unofficial Python wrapper for the decentralized exchange Uniswap"
55
repository = "https://github.com/shanefontaine/uniswap-python"
66
readme = "README.md"

‎tests/test_uniswap.py

Copy file name to clipboardExpand all lines: tests/test_uniswap.py
+74-45Lines changed: 74 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
from time import sleep
1010

1111
from web3 import Web3
12+
from web3.types import Wei
1213

1314
from uniswap import Uniswap
1415
from uniswap.constants import ETH_ADDRESS
15-
from uniswap.exceptions import InsufficientBalance
16+
from uniswap.fee import FeeTier
17+
from uniswap.exceptions import InsufficientBalance, InvalidFeeTier
1618
from uniswap.tokens import get_tokens
1719
from uniswap.util import (
1820
_str_to_addr,
@@ -75,11 +77,11 @@ def test_assets(client: Uniswap):
7577
("USDC", 10_000 * ONE_USDC),
7678
]:
7779
token_addr = tokens[token_name]
78-
price = client.get_price_output(_str_to_addr(ETH_ADDRESS), token_addr, amount)
80+
price = client.get_price_output(_str_to_addr(ETH_ADDRESS), token_addr, amount, fee=FeeTier.TIER_3000)
7981
logger.info(f"Cost of {amount} {token_name}: {price}")
8082
logger.info("Buying...")
8183

82-
txid = client.make_trade_output(tokens["ETH"], token_addr, amount)
84+
txid = client.make_trade_output(tokens["ETH"], token_addr, amount, fee=FeeTier.TIER_3000)
8385
tx = client.w3.eth.wait_for_transaction_receipt(txid, timeout=RECEIPT_TIMEOUT)
8486
assert tx["status"] == 1, f"Transaction failed: {tx}"
8587

@@ -159,47 +161,47 @@ def test_get_fee_taker(self, client: Uniswap):
159161

160162
# ------ Market --------------------------------------------------------------------
161163
@pytest.mark.parametrize(
162-
"token0, token1, qty, kwargs",
164+
"token0, token1, qty",
163165
[
164-
("ETH", "UNI", ONE_ETH, {}),
165-
("UNI", "ETH", ONE_ETH, {}),
166-
("ETH", "DAI", ONE_ETH, {}),
167-
("DAI", "ETH", ONE_ETH, {}),
168-
("ETH", "UNI", 2 * ONE_ETH, {}),
169-
("UNI", "ETH", 2 * ONE_ETH, {}),
170-
("WETH", "DAI", ONE_ETH, {}),
171-
("DAI", "WETH", ONE_ETH, {}),
172-
("DAI", "USDC", ONE_ETH, {"fee": 500}),
166+
("ETH", "UNI", ONE_ETH),
167+
("UNI", "ETH", ONE_ETH),
168+
("ETH", "DAI", ONE_ETH),
169+
("DAI", "ETH", ONE_ETH),
170+
("ETH", "UNI", 2 * ONE_ETH),
171+
("UNI", "ETH", 2 * ONE_ETH),
172+
("WETH", "DAI", ONE_ETH),
173+
("DAI", "WETH", ONE_ETH),
174+
("DAI", "USDC", ONE_ETH),
173175
],
174176
)
175-
def test_get_price_input(self, client, tokens, token0, token1, qty, kwargs):
177+
def test_get_price_input(self, client: Uniswap, tokens, token0, token1, qty):
176178
token0, token1 = tokens[token0], tokens[token1]
177179
if client.version == 1 and ETH_ADDRESS not in [token0, token1]:
178180
pytest.skip("Not supported in this version of Uniswap")
179-
r = client.get_price_input(token0, token1, qty, **kwargs)
181+
r = client.get_price_input(token0, token1, qty, fee=FeeTier.TIER_3000)
180182
assert r
181183

182184
@pytest.mark.parametrize(
183-
"token0, token1, qty, kwargs",
185+
"token0, token1, qty",
184186
[
185-
("ETH", "UNI", ONE_ETH, {}),
186-
("UNI", "ETH", ONE_ETH // 100, {}),
187-
("ETH", "DAI", ONE_ETH, {}),
188-
("DAI", "ETH", ONE_ETH, {}),
189-
("ETH", "UNI", 2 * ONE_ETH, {}),
190-
("WETH", "DAI", ONE_ETH, {}),
191-
("DAI", "WETH", ONE_ETH, {}),
192-
("DAI", "USDC", ONE_USDC, {"fee": 500}),
187+
("ETH", "UNI", ONE_ETH),
188+
("UNI", "ETH", ONE_ETH // 100),
189+
("ETH", "DAI", ONE_ETH),
190+
("DAI", "ETH", ONE_ETH),
191+
("ETH", "UNI", 2 * ONE_ETH),
192+
("WETH", "DAI", ONE_ETH),
193+
("DAI", "WETH", ONE_ETH),
194+
("DAI", "USDC", ONE_USDC),
193195
],
194196
)
195-
def test_get_price_output(self, client, tokens, token0, token1, qty, kwargs):
197+
def test_get_price_output(self, client: Uniswap, tokens, token0, token1, qty):
196198
token0, token1 = tokens[token0], tokens[token1]
197199
if client.version == 1 and ETH_ADDRESS not in [token0, token1]:
198200
pytest.skip("Not supported in this version of Uniswap")
199-
r = client.get_price_output(token0, token1, qty, **kwargs)
201+
r = client.get_price_output(token0, token1, qty, fee=FeeTier.TIER_3000)
200202
assert r
201203

202-
@pytest.mark.parametrize("token0, token1, fee", [("DAI", "USDC", 500)])
204+
@pytest.mark.parametrize("token0, token1, fee", [("DAI", "USDC", FeeTier.TIER_3000)])
203205
def test_get_raw_price(self, client: Uniswap, tokens, token0, token1, fee):
204206
token0, token1 = tokens[token0], tokens[token1]
205207
if client.version == 1:
@@ -210,7 +212,7 @@ def test_get_raw_price(self, client: Uniswap, tokens, token0, token1, fee):
210212
@pytest.mark.parametrize(
211213
"token0, token1, kwargs",
212214
[
213-
("WETH", "DAI", {"fee": 500}),
215+
("WETH", "DAI", {"fee": FeeTier.TIER_3000}),
214216
],
215217
)
216218
def test_get_pool_instance(self, client, tokens, token0, token1, kwargs):
@@ -223,7 +225,7 @@ def test_get_pool_instance(self, client, tokens, token0, token1, kwargs):
223225
@pytest.mark.parametrize(
224226
"token0, token1, kwargs",
225227
[
226-
("WETH", "DAI", {"fee": 500}),
228+
("WETH", "DAI", {"fee": FeeTier.TIER_3000}),
227229
],
228230
)
229231
def test_get_pool_immutables(self, client, tokens, token0, token1, kwargs):
@@ -238,7 +240,7 @@ def test_get_pool_immutables(self, client, tokens, token0, token1, kwargs):
238240
@pytest.mark.parametrize(
239241
"token0, token1, kwargs",
240242
[
241-
("WETH", "DAI", {"fee": 500}),
243+
("WETH", "DAI", {"fee": FeeTier.TIER_3000}),
242244
],
243245
)
244246
def test_get_pool_state(self, client, tokens, token0, token1, kwargs):
@@ -253,7 +255,7 @@ def test_get_pool_state(self, client, tokens, token0, token1, kwargs):
253255
@pytest.mark.parametrize(
254256
"amount0, amount1, token0, token1, kwargs",
255257
[
256-
(1, 10, "WETH", "DAI", {"fee": 500}),
258+
(1, 10, "WETH", "DAI", {"fee": FeeTier.TIER_3000}),
257259
],
258260
)
259261
def test_mint_position(
@@ -308,7 +310,7 @@ def test_get_exchange_rate(
308310
@pytest.mark.parametrize(
309311
"token0, token1, amount0, amount1, qty, fee",
310312
[
311-
("DAI", "USDC", ONE_ETH, ONE_USDC, ONE_ETH, 3000),
313+
("DAI", "USDC", ONE_ETH, ONE_USDC, ONE_ETH, FeeTier.TIER_3000),
312314
],
313315
)
314316
def test_v3_deploy_pool_with_liquidity(
@@ -325,14 +327,14 @@ def test_v3_deploy_pool_with_liquidity(
325327
print(pool.address)
326328
# Ensuring client has sufficient balance of both tokens
327329
eth_to_dai = client.make_trade(
328-
tokens["ETH"], tokens[token0], qty, client.address
330+
tokens["ETH"], tokens[token0], qty, client.address, fee=fee,
329331
)
330332
eth_to_dai_tx = client.w3.eth.wait_for_transaction_receipt(
331333
eth_to_dai, timeout=RECEIPT_TIMEOUT
332334
)
333335
assert eth_to_dai_tx["status"]
334336
dai_to_usdc = client.make_trade(
335-
tokens[token0], tokens[token1], qty * 10, client.address
337+
tokens[token0], tokens[token1], qty * 10, client.address, fee=fee,
336338
)
337339
dai_to_usdc_tx = client.w3.eth.wait_for_transaction_receipt(
338340
dai_to_usdc, timeout=RECEIPT_TIMEOUT
@@ -381,7 +383,7 @@ def test_get_tvl_in_pool_on_chain(self, client: Uniswap, tokens, token0, token1)
381383
if client.version != 3:
382384
pytest.skip("Not supported in this version of Uniswap")
383385

384-
pool = client.get_pool_instance(tokens[token0], tokens[token1])
386+
pool = client.get_pool_instance(tokens[token0], tokens[token1], fee=FeeTier.TIER_3000)
385387
tvl_0, tvl_1 = client.get_tvl_in_pool(pool)
386388
assert tvl_0 > 0
387389
assert tvl_1 > 0
@@ -452,7 +454,7 @@ def test_make_trade(
452454
with expectation():
453455
bal_in_before = client.get_token_balance(input_token)
454456

455-
txid = client.make_trade(input_token, output_token, qty, recipient)
457+
txid = client.make_trade(input_token, output_token, qty, recipient, fee=FeeTier.TIER_3000)
456458
tx = web3.eth.wait_for_transaction_receipt(txid, timeout=RECEIPT_TIMEOUT)
457459
assert tx["status"], f"Transaction failed with status {tx['status']}: {tx}"
458460

@@ -474,13 +476,6 @@ def test_make_trade(
474476
# ("ETH", "UNI", int(0.000001 * ONE_ETH), ZERO_ADDRESS),
475477
# ("UNI", "ETH", int(0.000001 * ONE_ETH), ZERO_ADDRESS),
476478
# ("DAI", "UNI", int(0.000001 * ONE_ETH), ZERO_ADDRESS),
477-
(
478-
"DAI",
479-
"ETH",
480-
10 * ONE_ETH,
481-
None,
482-
lambda: pytest.raises(InsufficientBalance),
483-
),
484479
("DAI", "DAI", ONE_USDC, None, lambda: pytest.raises(ValueError)),
485480
],
486481
)
@@ -504,11 +499,45 @@ def test_make_trade_output(
504499
with expectation():
505500
balance_before = client.get_token_balance(output_token)
506501

507-
r = client.make_trade_output(input_token, output_token, qty, recipient)
502+
r = client.make_trade_output(input_token, output_token, qty, recipient, fee=FeeTier.TIER_3000)
508503
tx = web3.eth.wait_for_transaction_receipt(r, timeout=RECEIPT_TIMEOUT)
509504
assert tx["status"]
510505

511-
# TODO: Checks for ETH, taking gas into account
506+
# # TODO: Checks for ETH, taking gas into account
512507
balance_after = client.get_token_balance(output_token)
513508
if output_token != tokens["ETH"]:
514509
assert balance_before + qty == balance_after
510+
511+
def test_fee_required_for_uniswap_v3(
512+
self,
513+
client: Uniswap,
514+
tokens,
515+
) -> None:
516+
if client.version != 3:
517+
pytest.skip("Not supported in this version of Uniswap")
518+
with pytest.raises(InvalidFeeTier):
519+
client.get_price_input(tokens["ETH"], tokens["UNI"], ONE_ETH, fee=None)
520+
with pytest.raises(InvalidFeeTier):
521+
client.get_price_output(tokens["ETH"], tokens["UNI"], ONE_ETH, fee=None)
522+
with pytest.raises(InvalidFeeTier):
523+
client._get_eth_token_output_price(tokens["UNI"], ONE_ETH, fee=None)
524+
with pytest.raises(InvalidFeeTier):
525+
client._get_token_eth_output_price(tokens["UNI"], Wei(ONE_ETH), fee=None)
526+
with pytest.raises(InvalidFeeTier):
527+
client._get_token_token_output_price(
528+
tokens["UNI"], tokens["ETH"], ONE_ETH, fee=None
529+
)
530+
with pytest.raises(InvalidFeeTier):
531+
client.make_trade(tokens["ETH"], tokens["UNI"], ONE_ETH, fee=None)
532+
with pytest.raises(InvalidFeeTier):
533+
client.make_trade_output(tokens["ETH"], tokens["UNI"], ONE_ETH, fee=None)
534+
# NOTE: (rudiemeant@gmail.com): Since in 0.7.1 we're breaking the
535+
# backwards-compatibility with 0.7.0, we should check
536+
# that clients now get an error when trying to call methods
537+
# without explicitly specifying a fee tier.
538+
with pytest.raises(InvalidFeeTier):
539+
client.get_pool_instance(tokens["ETH"], tokens["UNI"], fee=None) # type: ignore[arg-type]
540+
with pytest.raises(InvalidFeeTier):
541+
client.create_pool_instance(tokens["ETH"], tokens["UNI"], fee=None) # type: ignore[arg-type]
542+
with pytest.raises(InvalidFeeTier):
543+
client.get_raw_price(tokens["ETH"], tokens["UNI"], fee=None)

‎tests/units/__init__.py

Copy file name to clipboard
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+


‎tests/units/test_fee_tier.py

Copy file name to clipboard
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from typing import Any
2+
3+
import pytest
4+
5+
from uniswap.fee import FeeTier, validate_fee_tier
6+
from uniswap.exceptions import InvalidFeeTier
7+
8+
9+
10+
@pytest.mark.parametrize("version", [1, 2])
11+
def test_fee_tier_default(version: int) -> None:
12+
fee_tier = validate_fee_tier(fee=None, version=version)
13+
assert fee_tier == FeeTier.TIER_3000
14+
15+
16+
def test_fee_tier_default_v3() -> None:
17+
with pytest.raises(InvalidFeeTier) as exc:
18+
validate_fee_tier(fee=None, version=3)
19+
assert "Explicit fee tier is required for Uniswap V3" in str(exc.value)
20+
21+
22+
@pytest.mark.parametrize(
23+
("fee", "version"),
24+
[
25+
(FeeTier.TIER_100, 1),
26+
(FeeTier.TIER_500, 1),
27+
(FeeTier.TIER_10000, 1),
28+
(FeeTier.TIER_100, 2),
29+
(FeeTier.TIER_500, 2),
30+
(FeeTier.TIER_10000, 2),
31+
],
32+
)
33+
def test_unsupported_fee_tiers(fee: int, version: int) -> None:
34+
with pytest.raises(InvalidFeeTier) as exc:
35+
validate_fee_tier(fee=fee, version=version)
36+
assert "Unsupported fee tier" in str(exc.value)
37+
38+
39+
@pytest.mark.parametrize(
40+
"invalid_fee",
41+
[
42+
"undefined",
43+
0,
44+
1_000_000,
45+
1.1,
46+
(1, 3),
47+
type,
48+
],
49+
)
50+
def test_invalid_fee_tiers(invalid_fee: Any) -> None:
51+
with pytest.raises(InvalidFeeTier) as exc:
52+
validate_fee_tier(fee=invalid_fee, version=3)
53+
assert "Invalid fee tier" in str(exc.value)

‎uniswap/cli.py

Copy file name to clipboardExpand all lines: uniswap/cli.py
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from web3 import Web3
88

99
from .constants import ETH_ADDRESS
10+
from .fee import FeeTier
1011
from .token import BaseToken
1112
from .tokens import get_tokens
1213
from .uniswap import AddressLike, Uniswap, _str_to_addr
@@ -80,7 +81,7 @@ def price(
8081
else:
8182
decimals = uni.get_token(token_in).decimals
8283
quantity = 10**decimals
83-
price = uni.get_price_input(token_in, token_out, qty=quantity)
84+
price = uni.get_price_input(token_in, token_out, qty=quantity, fee=FeeTier.TIER_3000)
8485
if raw:
8586
click.echo(price)
8687
else:

‎uniswap/exceptions.py

Copy file name to clipboardExpand all lines: uniswap/exceptions.py
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,9 @@ class InsufficientBalance(Exception):
1313

1414
def __init__(self, had: int, needed: int) -> None:
1515
Exception.__init__(self, f"Insufficient balance. Had {had}, needed {needed}")
16+
17+
18+
class InvalidFeeTier(Exception):
19+
"""
20+
Raised when an invalid or unsupported fee tier is used.
21+
"""

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.