Skip to content

Commit 2df147d

Browse files
committed
feat: wip work on custom routes for v2 and v3
1 parent 42900d1 commit 2df147d

File tree

2 files changed

+94
-70
lines changed

2 files changed

+94
-70
lines changed

tests/test_uniswap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def client(request, web3: Web3, ganache: GanacheInstance):
4343
ganache.eth_privkey,
4444
web3=web3,
4545
version=request.param,
46-
use_estimate_gas=False, # see note in _build_and_send_tx
46+
use_estimate_gas=True, # see note in _build_and_send_tx
4747
)
4848

4949

uniswap/uniswap.py

Lines changed: 93 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ def get_price_input(
182182
token1: AddressLike, # output token
183183
qty: int,
184184
fee: int = None,
185-
route: Optional[List[AddressLike]] = None,
185+
route: List[AddressLike] = None,
186186
) -> int:
187187
"""Given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`."""
188188
if fee is None:
@@ -203,7 +203,7 @@ def get_price_output(
203203
token1: AddressLike,
204204
qty: int,
205205
fee: int = None,
206-
route: Optional[List[AddressLike]] = None,
206+
route: List[AddressLike] = None,
207207
) -> int:
208208
"""Returns the minimum amount of `token0` required to buy `qty` amount of `token1`."""
209209
if fee is None:
@@ -264,7 +264,7 @@ def _get_token_token_input_price(
264264
token1: AddressLike, # output token
265265
qty: int,
266266
fee: int,
267-
route: Optional[List[AddressLike]] = None,
267+
route: List[AddressLike] = None,
268268
) -> int:
269269
"""
270270
Public price (i.e. amount of output token received) for token to token trades with an exact input.
@@ -581,68 +581,93 @@ def _token_to_eth_swap_input(
581581
function = token_funcs.tokenToEthTransferInput(*func_params)
582582
return self._build_and_send_tx(function)
583583
elif self.version == 2:
584-
if recipient is None:
585-
recipient = self.address
586-
amount_out_min = int(
587-
(1 - slippage) * self._get_token_eth_input_price(input_token, qty, fee)
588-
)
589-
if fee_on_transfer:
590-
func = (
591-
self.router.functions.swapExactTokensForETHSupportingFeeOnTransferTokens
592-
)
593-
else:
594-
func = self.router.functions.swapExactTokensForETH
595-
return self._build_and_send_tx(
596-
func(
597-
qty,
598-
amount_out_min,
599-
[input_token, self.get_weth_address()],
600-
recipient,
601-
self._deadline(),
602-
),
584+
return self._token_to_eth_swap_input_v2(
585+
input_token, qty, recipient, fee, slippage, fee_on_transfer
603586
)
604587
elif self.version == 3:
605-
if recipient is None:
606-
recipient = self.address
607-
608588
if fee_on_transfer:
609589
raise Exception("fee on transfer not supported by Uniswap v3")
610590

611-
output_token = self.get_weth_address()
612-
min_tokens_bought = int(
613-
(1 - slippage)
614-
* self._get_token_eth_input_price(input_token, qty, fee=fee)
591+
return self._token_to_eth_swap_input_v3(
592+
input_token, qty, recipient, fee, slippage
615593
)
616-
sqrtPriceLimitX96 = 0
594+
else:
595+
raise ValueError
617596

618-
swap_data = self.router.encodeABI(
619-
fn_name="exactInputSingle",
620-
args=[
621-
(
622-
input_token,
623-
output_token,
624-
fee,
625-
ETH_ADDRESS,
626-
self._deadline(),
627-
qty,
628-
min_tokens_bought,
629-
sqrtPriceLimitX96,
630-
)
631-
],
597+
def _token_to_eth_swap_input_v2(
598+
self,
599+
input_token: AddressLike,
600+
qty: int,
601+
recipient: Optional[AddressLike],
602+
fee: int,
603+
slippage: float,
604+
fee_on_transfer: bool,
605+
) -> HexBytes:
606+
if recipient is None:
607+
recipient = self.address
608+
amount_out_min = int(
609+
(1 - slippage) * self._get_token_eth_input_price(input_token, qty, fee)
610+
)
611+
if fee_on_transfer:
612+
func = (
613+
self.router.functions.swapExactTokensForETHSupportingFeeOnTransferTokens
632614
)
615+
else:
616+
func = self.router.functions.swapExactTokensForETH
617+
return self._build_and_send_tx(
618+
func(
619+
qty,
620+
amount_out_min,
621+
[input_token, self.get_weth_address()],
622+
recipient,
623+
self._deadline(),
624+
),
625+
)
633626

634-
unwrap_data = self.router.encodeABI(
635-
fn_name="unwrapWETH9", args=[min_tokens_bought, recipient]
636-
)
627+
def _token_to_eth_swap_input_v3(
628+
self,
629+
input_token: AddressLike,
630+
qty: int,
631+
recipient: Optional[AddressLike],
632+
fee: int,
633+
slippage: float,
634+
) -> HexBytes:
635+
"""NOTE: Should always be called via the dispatcher `_token_to_eth_swap_input`"""
636+
if recipient is None:
637+
recipient = self.address
637638

638-
# Multicall
639-
return self._build_and_send_tx(
640-
self.router.functions.multicall([swap_data, unwrap_data]),
641-
self._get_tx_params(),
642-
)
639+
output_token = self.get_weth_address()
640+
min_tokens_bought = int(
641+
(1 - slippage) * self._get_token_eth_input_price(input_token, qty, fee=fee)
642+
)
643+
sqrtPriceLimitX96 = 0
644+
645+
swap_data = self.router.encodeABI(
646+
fn_name="exactInputSingle",
647+
args=[
648+
(
649+
input_token,
650+
output_token,
651+
fee,
652+
ETH_ADDRESS,
653+
self._deadline(),
654+
qty,
655+
min_tokens_bought,
656+
sqrtPriceLimitX96,
657+
)
658+
],
659+
)
643660

644-
else:
645-
raise ValueError
661+
# NOTE: This will probably lead to dust WETH accumulation
662+
unwrap_data = self.router.encodeABI(
663+
fn_name="unwrapWETH9", args=[min_tokens_bought, recipient]
664+
)
665+
666+
# Multicall
667+
return self._build_and_send_tx(
668+
self.router.functions.multicall([swap_data, unwrap_data]),
669+
self._get_tx_params(),
670+
)
646671

647672
def _token_to_token_swap_input(
648673
self,
@@ -1111,13 +1136,17 @@ def _build_and_send_tx(
11111136
# `use_estimate_gas` needs to be True for networks like Arbitrum (can't assume 250000 gas),
11121137
# but it breaks tests for unknown reasons because estimateGas takes forever on some tx's.
11131138
# Maybe an issue with ganache? (got GC warnings once...)
1139+
1140+
# In case gas estimation is disabled.
1141+
# Without this set before gas estimation, it can lead to ganache stack overflow.
1142+
# See: https://github.com/trufflesuite/ganache/issues/985#issuecomment-998937085
1143+
transaction["gas"] = Wei(250000)
1144+
11141145
if self.use_estimate_gas:
11151146
# The Uniswap V3 UI uses 20% margin for transactions
11161147
transaction["gas"] = Wei(
11171148
int(self.w3.eth.estimate_gas(transaction) * 1.2)
11181149
)
1119-
else:
1120-
transaction["gas"] = Wei(250000)
11211150

11221151
signed_txn = self.w3.eth.account.sign_transaction(
11231152
transaction, private_key=self.private_key
@@ -1225,11 +1254,11 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token
12251254
raise InvalidToken(address)
12261255
try:
12271256
name = _name.decode()
1228-
except:
1257+
except Exception: # FIXME: Be more precise about exception to catch
12291258
name = _name
12301259
try:
12311260
symbol = _symbol.decode()
1232-
except:
1261+
except Exception: # FIXME: Be more precise about exception to catch
12331262
symbol = _symbol
12341263
return ERC20Token(symbol, address, name, decimals)
12351264

@@ -1256,11 +1285,11 @@ def get_raw_price(
12561285
if token_out == ETH_ADDRESS:
12571286
token_out = self.get_weth_address()
12581287

1288+
params: Tuple[ChecksumAddress, ChecksumAddress] = (
1289+
self.w3.toChecksumAddress(token_in),
1290+
self.w3.toChecksumAddress(token_out),
1291+
)
12591292
if self.version == 2:
1260-
params: Iterable[Union[ChecksumAddress,Optional[int]]] = [
1261-
self.w3.toChecksumAddress(token_in),
1262-
self.w3.toChecksumAddress(token_out),
1263-
]
12641293
pair_token = self.factory_contract.functions.getPair(*params).call()
12651294
token_in_erc20 = _load_contract_erc20(
12661295
self.w3, self.w3.toChecksumAddress(token_in)
@@ -1286,12 +1315,7 @@ def get_raw_price(
12861315

12871316
raw_price = token_out_balance / token_in_balance
12881317
else:
1289-
params = [
1290-
self.w3.toChecksumAddress(token_in),
1291-
self.w3.toChecksumAddress(token_out),
1292-
fee,
1293-
]
1294-
pool_address = self.factory_contract.functions.getPool(*params).call()
1318+
pool_address = self.factory_contract.functions.getPool(*params, fee).call()
12951319
pool_contract = _load_contract(
12961320
self.w3, abi_name="uniswap-v3/pool", address=pool_address
12971321
)
@@ -1317,7 +1341,7 @@ def estimate_price_impact(
13171341
token_out: AddressLike,
13181342
amount_in: int,
13191343
fee: int = None,
1320-
route: Optional[List[AddressLike]] = None,
1344+
route: List[AddressLike] = None,
13211345
) -> float:
13221346
"""
13231347
Returns the estimated price impact as a positive float (0.01 = 1%).

0 commit comments

Comments
 (0)