Skip to content

Add more v4 tests#456

Open
liquid-8 wants to merge 19 commits into
uniswap-python:dev/v4-finfrom
liquid-8:uniswap4_RC
Open

Add more v4 tests#456
liquid-8 wants to merge 19 commits into
uniswap-python:dev/v4-finfrom
liquid-8:uniswap4_RC

Conversation

@liquid-8
Copy link
Copy Markdown
Member

No description provided.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 17, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.45%. Comparing base (11b1373) to head (9fd0723).
⚠️ Report is 21 commits behind head on dev/v4-fin.

Additional details and impacted files
@@               Coverage Diff               @@
##           dev/v4-fin     #456       +/-   ##
===============================================
+ Coverage       36.77%   70.45%   +33.68%     
===============================================
  Files              12       12               
  Lines            2238     2244        +6     
===============================================
+ Hits              823     1581      +758     
+ Misses           1415      663      -752     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 17, 2026

Greptile Summary

This PR fixes several bugs in the v4 swap pipeline (double-encoding of multi-hop routes, wrong token argument in get_price_output exact-output path, inconsistent custom_nonce propagation to _build_and_send_tx) and converts encode_path_keys_input/encode_path_keys_output to @staticmethod. It also adds a new update_last_nonce() helper and fills in the previously stub-only test suite with full integration tests covering approvals, price quotes, single- and multi-hop swaps, liquidity operations, and the V4pools pool-discovery service.

  • Production fixes: get_price_output multi-hop path now correctly passes token1 (the exact output token) to the quoter; multi-hop swap functions (token_to_token_swap_input, token_to_token_swap_output) now encode the route internally so callers no longer risk passing a pre-encoded route through make_swap_input/make_swap_output.
  • Nonce management: _build_and_send_tx nonce-increment guard changed from a nonce-value comparison to if custom_nonce is None, and update_last_nonce() is added to re-sync the in-memory nonce after external or custom-nonce transactions.
  • Tests: Pool-key module-level constants replace per-test fixtures; new parametrized tests exercise the full swap, quote, and liquidity API surface against an Anvil mainnet fork.

Confidence Score: 3/5

Several correctness issues flagged in earlier review rounds remain unaddressed — most notably the inflated gas budget in drop_txn and the stale-nonce risk in update_last_nonce — and a new assertion in test_get_position_info is structurally broken due to a hex-prefix mismatch.

The swap double-encoding fix and the get_price_output token-argument fix are genuine improvements, and the test suite is now far more substantive than before. However, drop_txn still uses self.gas_limit instead of the required 21 000 gas for a plain ETH transfer, update_last_nonce still queries 'latest' rather than 'pending' (risking nonce reuse), and test_get_position_info's pool-ID comparison will always fail because hex() produces a '0x'-prefixed string while bytes.hex() does not. These pre-existing unresolved findings keep confidence below the midpoint.

uniswap/uniswap4.py (drop_txn gas budget, update_last_nonce block specifier) and tests/test_uniswap4.py (test_get_position_info pool-ID assertion) need attention before merge.

Important Files Changed

Filename Overview
uniswap/uniswap4.py Multiple bug fixes (double-encoding in multi-hop routes, wrong token in get_price_output, nonce update logic, static method conversions) and new update_last_nonce()/drop_txn changes; previously flagged issues (drop_txn gas_limit, update_last_nonce block specifier) remain unresolved.
tests/test_uniswap4.py Adds substantive integration tests for swaps, approvals, drop_txn, liquidity, V4pools, and price quotes; previously flagged issues (hex prefix mismatch in test_get_position_info, drop_txn address semantics) remain in part.
tests/pool_list.test New test data file containing XML-encoded pool list used by test_load_poolkeys_list and test_get_poolkeys_sublist.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["make_swap_input / make_swap_output"] --> B{route is None?}
    B -- Yes --> C["token_to_token_swap_exact_input/output\n(single-hop, uses pool_key directly)"]
    B -- No --> D["token_to_token_swap_input/output\n(now encodes route internally)"]
    D --> E["encode_path_keys_input/output\n(@staticmethod)"]
    E --> F["_build_and_send_tx\n(custom_nonce passed through)"]
    C --> F
    F --> G{custom_nonce is None?}
    G -- Yes --> H["last_nonce = tx nonce + 1"]
    G -- No --> I["last_nonce unchanged\n(caller must call update_last_nonce)"]
    H --> J["send_raw_transaction"]
    I --> J
Loading

Reviews (17): Last reviewed commit: "Add the rest of pool data tests" | Re-trigger Greptile

Comment thread tests/test_uniswap4.py Outdated
Comment thread tests/test_uniswap4.py Outdated
Comment thread uniswap/uniswap4.py
Comment thread tests/test_uniswap4.py
Comment on lines +556 to +570
"token_id, token0_decimals, token1_decimals",
[(TOKEN_ID, ETH_DECIMALS, USDC_DECIMALS)],
)
def test_get_position_value(
self,
client: Uniswap4,
token_id: int,
token0_decimals: int,
token1_decimals: int,
):
result = client.get_position_value(token_id, token0_decimals, token1_decimals)
assert result

@pytest.mark.parametrize(
"transaction_hash",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Pool ID hex prefix mismatch makes assertion always fail

hex(int_value) in Python always returns a string prefixed with "0x" (e.g. "0x3f4a..."), while bytes.hex() returns plain lowercase hex with no prefix (e.g. "3f4a..."). The slice comparison truncated_pool_id_str == pool_id_str[:len(truncated_pool_id_str)] will therefore never be True — the first two characters ("0x") in truncated_pool_id_str can never match the leading hex digits of pool_id_str. Strip the prefix with [2:] when building truncated_pool_id_str.

Comment thread tests/test_uniswap4.py
Comment thread uniswap/uniswap4.py
Comment on lines +1939 to +1957
"gas": int(self.gas_limit),
"chainId": int(self.w3.eth.chain_id),
}
signed_txn = self.w3.eth.account.sign_transaction(
dict(
chainId=int(self.w3.net.version),
nonce=self.last_nonce if custom_nonce is None else custom_nonce,
gasPrice=Web3.to_wei(gas_price, "gwei"),
gas=int(21000),
to=Web3.to_checksum_address(address_to),
value=Web3.to_wei(0, "wei"),
),
transaction_dict_legacy,
self.private_key,
)
# This one is for post-Merge transactions
transaction_dict = {
"type": 2,
"nonce": self.w3.eth.get_transaction_count(self.address)
if custom_nonce is None
else custom_nonce,
"from": _addr_to_str(self.address),
"to": _addr_to_str(address_to),
"value": Web3.to_wei(0, "wei"),
"maxFeePerGas": Web3.to_wei(int(gas_price), "gwei"),
"maxPriorityFeePerGas": Web3.to_wei(priority_fee, "gwei"),
"gas": int(self.gas_limit),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 drop_txn now uses self.gas_limit instead of the required 21 000 gas for a plain ETH transfer

A zero-value ETH transfer unconditionally consumes exactly 21 000 gas. Validators require that balance ≥ value + gas_limit × max_fee. Using self.gas_limit (typically a large contract-interaction budget, e.g. 6 M gas) inflates the pre-execution balance check: at a modest 50 gwei the node requires 6 000 000 × 50 gwei = 0.3 ETH just to admit the transaction. This makes drop_txn most likely to fail precisely when it is needed most — when the account is running low. Both transaction_dict_legacy and transaction_dict carry this regression and should be restored to "gas": 21_000.

Comment thread tests/test_uniswap4.py
Comment on lines +831 to +878
@pytest.mark.parametrize(
"token0, token1, qty, pool_key, hook_data, route",
[
(
ETH_ADDRESS,
USDC_ADDRESS,
1000 * ONE_USDC,
eth_usdc_poolkey,
b"",
None,
),
(
USDC_ADDRESS,
ETH_ADDRESS,
ONE_ETH,
eth_usdc_poolkey,
b"",
None,
),
(
ETH_ADDRESS,
USDT_ADDRESS,
ONE_ETH,
None,
None,
None,
None,
[
usdc_usdt_poolkey,
eth_usdc_poolkey,
],
),
(
USDT_ADDRESS,
ETH_ADDRESS,
1000 * ONE_USDT,
None,
None,
None,
None,
[
eth_usdc_poolkey,
usdc_usdt_poolkey,
],
),
],
)
def test_make_swap_output(
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 test_make_swap_output has the same parametrize mismatch as test_make_swap_input. The decorator names 6 fields ("token0, token1, qty, pool_key, hook_data, route"), but the function signature has a 7th parameter custom_nonce that isn't covered. Cases 3–4 each provide 8 values — the extra None, None at positions 4–5 shifts route to position 8, creating a tuple-length mismatch that causes pytest to raise a ValueError and refuse to collect those parametrize cases entirely.

Suggested change
@pytest.mark.parametrize(
"token0, token1, qty, pool_key, hook_data, route",
[
(
ETH_ADDRESS,
USDC_ADDRESS,
1000 * ONE_USDC,
eth_usdc_poolkey,
b"",
None,
),
(
USDC_ADDRESS,
ETH_ADDRESS,
ONE_ETH,
eth_usdc_poolkey,
b"",
None,
),
(
ETH_ADDRESS,
USDT_ADDRESS,
ONE_ETH,
None,
None,
None,
None,
[
usdc_usdt_poolkey,
eth_usdc_poolkey,
],
),
(
USDT_ADDRESS,
ETH_ADDRESS,
1000 * ONE_USDT,
None,
None,
None,
None,
[
eth_usdc_poolkey,
usdc_usdt_poolkey,
],
),
],
)
def test_make_swap_output(
@pytest.mark.parametrize(
"token0, token1, qty, pool_key, hook_data, route, custom_nonce",
[
(
ETH_ADDRESS,
USDC_ADDRESS,
1000 * ONE_USDC,
eth_usdc_poolkey,
b"",
None,
None,
),
(
USDC_ADDRESS,
ETH_ADDRESS,
ONE_ETH,
eth_usdc_poolkey,
b"",
None,
None,
),
(
ETH_ADDRESS,
USDT_ADDRESS,
ONE_ETH,
None,
None,
[
usdc_usdt_poolkey,
eth_usdc_poolkey,
],
None,
),
(
USDT_ADDRESS,
ETH_ADDRESS,
1000 * ONE_USDT,
None,
None,
[
eth_usdc_poolkey,
usdc_usdt_poolkey,
],
None,
),
],
)
def test_make_swap_output(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant