From 5ff88d5ec47bd535c72662ab696cd3b937190261 Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:16:05 +0300 Subject: [PATCH 01/10] Switch from ganachr to foundry-anvil for v4 tests --- .github/workflows/test.yml | 2 ++ tests/test_uniswap.py | 59 ++++++++++++++++++++++++-------------- tests/test_uniswap4.py | 45 ++++++++++++++--------------- 3 files changed, 61 insertions(+), 45 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d22714bf..e474a328 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,6 +73,8 @@ jobs: run: | poetry install npm install -g ganache@7.5.0 + wget -qO- https://foundry.paradigm.xyz | bash + foundryup - name: Test env: diff --git a/tests/test_uniswap.py b/tests/test_uniswap.py index 526921fc..6ecc6eb9 100644 --- a/tests/test_uniswap.py +++ b/tests/test_uniswap.py @@ -1,25 +1,25 @@ -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, ) logger = logging.getLogger(__name__) @@ -71,17 +71,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}" @@ -114,17 +117,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() @@ -136,7 +138,6 @@ def does_not_raise(): yield - ONE_ETH = 10**18 ONE_USDC = 10**6 @@ -201,7 +202,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: @@ -327,14 +330,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 @@ -383,7 +394,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 @@ -454,7 +467,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}" @@ -499,7 +514,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"] @@ -540,4 +557,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) \ No newline at end of file + client.get_raw_price(tokens["ETH"], tokens["UNI"], fee=None) diff --git a/tests/test_uniswap4.py b/tests/test_uniswap4.py index b95f1791..36e909e2 100644 --- a/tests/test_uniswap4.py +++ b/tests/test_uniswap4.py @@ -31,7 +31,7 @@ @dataclass -class GanacheInstance: +class AnvilInstance: provider: str eth_address: str eth_privkey: str @@ -60,53 +60,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`" ) 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" + 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() @@ -117,7 +114,7 @@ def does_not_raise(): @pytest.mark.usefixtures("client", "web3") -class TestUniswap(object): +class TestUniswap4(object): # ------ Market -------------------------------------------------------------------- # Input quotes @pytest.mark.parametrize( @@ -163,7 +160,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, From 1d79532155b5f53f97345a6084210c282e215ae9 Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:23:39 +0300 Subject: [PATCH 02/10] fix --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e474a328..9b4bc36c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -74,6 +74,7 @@ jobs: poetry install npm install -g ganache@7.5.0 wget -qO- https://foundry.paradigm.xyz | bash + source ~/.bashrc foundryup - name: Test From a0b5b802b1710578ab283e411dc340ad1a9868ce Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:29:06 +0300 Subject: [PATCH 03/10] fix --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b4bc36c..c512881c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -74,7 +74,8 @@ jobs: poetry install npm install -g ganache@7.5.0 wget -qO- https://foundry.paradigm.xyz | bash - source ~/.bashrc + source ~/.bashrc + source ~/.zshrc foundryup - name: Test From 6f02fc1c56dd31d23b89cdb06d75a0bea409d3cd Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:31:47 +0300 Subject: [PATCH 04/10] fix --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c512881c..6f1a8844 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -76,7 +76,7 @@ jobs: wget -qO- https://foundry.paradigm.xyz | bash source ~/.bashrc source ~/.zshrc - foundryup + ~/.foundry/bin/foundryup - name: Test env: From 53412753f1e3a553f024c8e88fc3f8f03e36d43e Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:32:04 +0300 Subject: [PATCH 05/10] fix --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6f1a8844..d92208da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,7 +75,6 @@ jobs: npm install -g ganache@7.5.0 wget -qO- https://foundry.paradigm.xyz | bash source ~/.bashrc - source ~/.zshrc ~/.foundry/bin/foundryup - name: Test From acf4e4ed13a5f1cb2e6006b592fe7a3281787226 Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:36:48 +0300 Subject: [PATCH 06/10] fix --- .github/workflows/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d92208da..407c0e85 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -74,8 +74,8 @@ jobs: poetry install npm install -g ganache@7.5.0 wget -qO- https://foundry.paradigm.xyz | bash - source ~/.bashrc - ~/.foundry/bin/foundryup + source /home/runner/.bashrc + foundryup - name: Test env: From 43fa4442a17335d178be529b82d27cf114efa698 Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 05:46:00 +0300 Subject: [PATCH 07/10] fix --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 407c0e85..160e6c4d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -73,9 +73,9 @@ jobs: run: | poetry install npm install -g ganache@7.5.0 - wget -qO- https://foundry.paradigm.xyz | bash - source /home/runner/.bashrc - foundryup + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 - name: Test env: From 1dcf975a6af7fae116a1a83b9f98e2da83de2928 Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 07:20:16 +0300 Subject: [PATCH 08/10] fix --- .github/workflows/test.yml | 2 +- tests/test_uniswap.py | 5 +++++ tests/test_uniswap4.py | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 160e6c4d..ed81287b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,7 @@ jobs: strategy: fail-fast: false matrix: - uniswap-version: [1, 2, 3] + uniswap-version: [1, 2, 3, 4] network: ["mainnet"] include: - network: arbitrum diff --git a/tests/test_uniswap.py b/tests/test_uniswap.py index 6ecc6eb9..9b3bbf1c 100644 --- a/tests/test_uniswap.py +++ b/tests/test_uniswap.py @@ -22,6 +22,11 @@ default_tick_range, ) +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__) logging.basicConfig(level=logging.INFO) diff --git a/tests/test_uniswap4.py b/tests/test_uniswap4.py index 36e909e2..9769114f 100644 --- a/tests/test_uniswap4.py +++ b/tests/test_uniswap4.py @@ -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) From 7bd29c1c01923330cf1e8bd1bb1808afe8f5776d Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 07:28:53 +0300 Subject: [PATCH 09/10] fix --- tests/test_cli.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_cli.py b/tests/test_cli.py index 90139ffb..5607c1f1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -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) From dcdae417a9e11c2ed7d92ce902c17e5993c917dd Mon Sep 17 00:00:00 2001 From: Yohan Key Date: Tue, 12 May 2026 07:36:32 +0300 Subject: [PATCH 10/10] fix --- .github/workflows/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ed81287b..697c1cff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,9 +22,9 @@ jobs: matrix: 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