Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ jobs:
strategy:
fail-fast: false
matrix:
uniswap-version: [1, 2, 3]
uniswap-version: [1, 2, 3, 4]
network: ["mainnet"]
include:
- network: arbitrum
uniswap-version: 3
# include:
# - network: arbitrum
# uniswap-version: 3
#include:
# - network: xdai
# uniswap-version: 3
Expand Down Expand Up @@ -74,6 +74,9 @@ jobs:
poetry install
npm install -g ganache@7.5.0

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Test
env:
# Use the secret if available, otherwise fallback to the public key
Expand Down
5 changes: 5 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

from uniswap.cli import main

pytestmark = pytest.mark.skipif(
os.getenv("UNISWAP_VERSION") == "4",
reason="Not supported in v4",
)


def print_result(result):
print(result)
Expand Down
64 changes: 43 additions & 21 deletions tests/test_uniswap.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import pytest
import logging
import os
import subprocess
import shutil
import logging
from typing import Generator
import subprocess
from contextlib import contextmanager
from dataclasses import dataclass
from time import sleep
from typing import Generator

import pytest
from web3 import Web3
from web3.types import Wei

from uniswap import Uniswap
from uniswap.constants import ETH_ADDRESS
from uniswap.exceptions import InvalidFeeTier
from uniswap.fee import FeeTier
from uniswap.exceptions import InsufficientBalance, InvalidFeeTier
from uniswap.tokens import get_tokens
from uniswap.util import (
_addr_to_str,
_str_to_addr,
default_tick_range,
_addr_to_str,
)

pytestmark = pytest.mark.skipif(
os.getenv("UNISWAP_VERSION") == "4",
reason="This test file is for Uniswap v1, v2, and v3. For Uniswap v4 tests, see test_uniswap4.py",
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -71,17 +76,20 @@ def test_assets(client: Uniswap):
"""
tokens = get_tokens(client.netname)


for token_name, amount in [
("DAI", 10_000 * ONE_DAI),
("USDC", 10_000 * ONE_USDC),
]:
token_addr = tokens[token_name]
price = client.get_price_output(_str_to_addr(ETH_ADDRESS), token_addr, amount, fee=FeeTier.TIER_3000)
price = client.get_price_output(
_str_to_addr(ETH_ADDRESS), token_addr, amount, fee=FeeTier.TIER_3000
)
logger.info(f"Cost of {amount} {token_name}: {price}")
logger.info("Buying...")

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

Expand Down Expand Up @@ -114,17 +122,16 @@ def ganache() -> Generator[GanacheInstance, None, None]:
--wallet.seed test
--chain.networkId 1
--chain.chainId 1
--fork.url {os.environ['PROVIDER']}
--fork.url {os.environ["PROVIDER"]}
--miner.defaultGasPrice {defaultGasPrice}
--miner.instamine "strict"
""".replace(
"\n", " "
),
""".replace("\n", " "),
shell=True,
)
# Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH
eth_address = "0x94e3361495bD110114ac0b6e35Ed75E77E6a6cFA"
eth_privkey = "0x6f1313062db38875fb01ee52682cbf6a8420e92bfbc578c5d4fdc0a32c50266f"

sleep(3)
yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey)
p.kill()
Expand All @@ -136,7 +143,6 @@ def does_not_raise():
yield



ONE_ETH = 10**18
ONE_USDC = 10**6

Expand Down Expand Up @@ -201,7 +207,9 @@ def test_get_price_output(self, client: Uniswap, tokens, token0, token1, qty):
r = client.get_price_output(token0, token1, qty, fee=FeeTier.TIER_3000)
assert r

@pytest.mark.parametrize("token0, token1, fee", [("DAI", "USDC", FeeTier.TIER_3000)])
@pytest.mark.parametrize(
"token0, token1, fee", [("DAI", "USDC", FeeTier.TIER_3000)]
)
def test_get_raw_price(self, client: Uniswap, tokens, token0, token1, fee):
token0, token1 = tokens[token0], tokens[token1]
if client.version == 1:
Expand Down Expand Up @@ -327,14 +335,22 @@ def test_v3_deploy_pool_with_liquidity(
print(pool.address)
# Ensuring client has sufficient balance of both tokens
eth_to_dai = client.make_trade(
tokens["ETH"], tokens[token0], qty, client.address, fee=fee,
tokens["ETH"],
tokens[token0],
qty,
client.address,
fee=fee,
)
eth_to_dai_tx = client.w3.eth.wait_for_transaction_receipt(
eth_to_dai, timeout=RECEIPT_TIMEOUT
)
assert eth_to_dai_tx["status"]
dai_to_usdc = client.make_trade(
tokens[token0], tokens[token1], qty * 10, client.address, fee=fee,
tokens[token0],
tokens[token1],
qty * 10,
client.address,
fee=fee,
)
dai_to_usdc_tx = client.w3.eth.wait_for_transaction_receipt(
dai_to_usdc, timeout=RECEIPT_TIMEOUT
Expand Down Expand Up @@ -383,7 +399,9 @@ def test_get_tvl_in_pool_on_chain(self, client: Uniswap, tokens, token0, token1)
if client.version != 3:
pytest.skip("Not supported in this version of Uniswap")

pool = client.get_pool_instance(tokens[token0], tokens[token1], fee=FeeTier.TIER_3000)
pool = client.get_pool_instance(
tokens[token0], tokens[token1], fee=FeeTier.TIER_3000
)
tvl_0, tvl_1 = client.get_tvl_in_pool(pool)
assert tvl_0 > 0
assert tvl_1 > 0
Expand Down Expand Up @@ -454,7 +472,9 @@ def test_make_trade(
with expectation():
bal_in_before = client.get_token_balance(input_token)

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

Expand Down Expand Up @@ -499,7 +519,9 @@ def test_make_trade_output(
with expectation():
balance_before = client.get_token_balance(output_token)

r = client.make_trade_output(input_token, output_token, qty, recipient, fee=FeeTier.TIER_3000)
r = client.make_trade_output(
input_token, output_token, qty, recipient, fee=FeeTier.TIER_3000
)
tx = web3.eth.wait_for_transaction_receipt(r, timeout=RECEIPT_TIMEOUT)
assert tx["status"]

Expand Down Expand Up @@ -540,4 +562,4 @@ def test_fee_required_for_uniswap_v3(
with pytest.raises(InvalidFeeTier):
client.create_pool_instance(tokens["ETH"], tokens["UNI"], fee=None) # type: ignore[arg-type]
with pytest.raises(InvalidFeeTier):
client.get_raw_price(tokens["ETH"], tokens["UNI"], fee=None)
client.get_raw_price(tokens["ETH"], tokens["UNI"], fee=None)
50 changes: 26 additions & 24 deletions tests/test_uniswap4.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
from uniswap.constants import ETH_ADDRESS, ZERO_HOOK
from uniswap.types import PoolKey

pytestmark = pytest.mark.skipif(
os.getenv("UNISWAP_VERSION") != "4",
reason="This test file is for Uniswap v4. For Uniswap v1, v2, and v3 tests, see test_uniswap.py",
)

logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

Expand All @@ -31,7 +36,7 @@


@dataclass
class GanacheInstance:
class AnvilInstance:
provider: str
eth_address: str
eth_privkey: str
Expand Down Expand Up @@ -60,53 +65,50 @@ def usdc_usdt_poolkey() -> PoolKey:


@pytest.fixture(scope="module")
def client(web3: Web3, ganache: GanacheInstance) -> Uniswap4:
def client(web3: Web3, anvil: AnvilInstance) -> Uniswap4:
return Uniswap4(
ganache.eth_address,
ganache.eth_privkey,
anvil.eth_address,
anvil.eth_privkey,
web3=web3,
)


@pytest.fixture(scope="module")
def web3(ganache: GanacheInstance) -> Web3:
w3 = Web3(Web3.HTTPProvider(ganache.provider, request_kwargs={"timeout": 30}))
def web3(anvil: AnvilInstance) -> Web3:
w3 = Web3(Web3.HTTPProvider(anvil.provider, request_kwargs={"timeout": 30}))
if 1 != int(w3.net.version):
logger.warning("PROVIDER was not a mainnet provider, which the tests require")
return w3


@pytest.fixture(scope="module")
def ganache() -> Generator[GanacheInstance, None, None]:
"""Fixture that runs ganache which has forked off mainnet"""
if not shutil.which("ganache"):
def anvil() -> Generator[AnvilInstance, None, None]:
"""Fixture that runs anvil which has forked off mainnet"""
if not shutil.which("anvil"):
raise Exception(
"ganache was not found in PATH, you can install it with `npm install -g ganache`"
"anvil was not found in PATH, you can install it with `npm install -g anvil`"
Comment thread
liquid-8 marked this conversation as resolved.
)
if "PROVIDER" not in os.environ:
raise Exception(
"PROVIDER was not set, you need to set it to a mainnet provider (such as Infura) so that we can fork off our testnet"
)

port = 10999
port = 10998
defaultGasPrice = 100_000_000_000 # 100 gwei
p = subprocess.Popen(
f"""ganache
f"""anvil
--port {port}
--wallet.seed test
--chain.networkId 1
--chain.chainId 1
--fork.url {os.environ["PROVIDER"]}
--miner.defaultGasPrice {defaultGasPrice}
--miner.instamine "strict"
--chain-id 1
--fork-url {os.environ["PROVIDER"]}
--gas-price {defaultGasPrice}
""".replace("\n", " "),
shell=True,
)
# Address #1 when ganache is run with `--wallet.seed test`, it starts with 1000 ETH
eth_address = "0x94e3361495bD110114ac0b6e35Ed75E77E6a6cFA"
eth_privkey = "0x6f1313062db38875fb01ee52682cbf6a8420e92bfbc578c5d4fdc0a32c50266f"
# Address #1 when anvil is run with `--wallet.seed test`, it starts with 1000 ETH
eth_address = "0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"
Comment thread
liquid-8 marked this conversation as resolved.
eth_privkey = "0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6"
sleep(3)
yield GanacheInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey)
yield AnvilInstance(f"http://127.0.0.1:{port}", eth_address, eth_privkey)
p.kill()
p.wait()

Expand All @@ -117,7 +119,7 @@ def does_not_raise():


@pytest.mark.usefixtures("client", "web3")
class TestUniswap(object):
class TestUniswap4(object):
# ------ Market --------------------------------------------------------------------
# Input quotes
@pytest.mark.parametrize(
Expand Down Expand Up @@ -163,7 +165,7 @@ def test_get_quote_exact_input_single(
(
ETH_ADDRESS,
USDC_ADDRESS,
ONE_ETH,
1000 * ONE_USDC,
ETH_USDC_FEE,
ETH_USDC_TICK_SPACING,
ZERO_HOOK,
Expand Down
Loading