Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

Python: Permit2 approval in CdpSmartWalletActionProvider.swap always reports failure (integer vs string receipt status)

Aperta Adatta ai principianti
#1,592 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

I maintainer di solito rispondono entro 1 giorno

@0xLittleDream ci sta già lavorando.

Dal 24/10/2026.

  • #1597 di @0xLittleDream — aperta

Valutazione

Difficoltà
2/5
Tempo stimato
1-3 ore
Idoneità per principianti
74/100
Tipo di issue
Bug
Chiarezza
Specificata chiaramente
Stato di attività
Attiva
Stack tecnologico
python

Direzione di ricerca

Leggi CdpSmartWalletActionProvider.swap() e CdpSmartWalletProvider.wait_for_transaction_receipt per confermare il tipo di stato della ricevuta di approvazione. Poi esamina tests/action_providers/cdp/test_cdp_smart_wallet_action_provider.py, il cui run_until_complete simulato salta la coroutine, ed esegui la riproduzione descritta nell’issue. Il lavoro è completato quando lo stato intero di successo 1 viene accettato e il test di approvazione esegue il ramo pertinente.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

CdpSmartWalletActionProvider.swap() sends a Permit2 approval transaction and then
checks the receipt:

receipt = wallet_provider.wait_for_transaction_receipt(approval_tx_hash)
if receipt.status != "complete":
    return {"success": False, "error": "Approval transaction failed"}

CdpSmartWalletProvider.wait_for_transaction_receipt returns
self._web3.eth.wait_for_transaction_receipt(...), a web3.py receipt whose status is
the integer 1 on success and 0 on revert. 1 != "complete" is always true, so a
successful approval is always reported as a failure.

This costs money rather than just being wrong. The approval is sent before the check
and approves the maximum:

args=[PERMIT2_ADDRESS, 2**256 - 1],  # Max uint256

So each attempt spends gas and grants an unlimited Permit2 allowance on-chain, and the
caller is told it failed. An agent that retries approves again. The swap never completes
through this path.

Why the existing test does not catch it. In
tests/action_providers/cdp/test_cdp_smart_wallet_action_provider.py,
test_swap_with_approval_transaction sets
mock_loop.run_until_complete.return_value = async_result, so the _execute_swap
coroutine containing the check never runs — pytest reports
RuntimeWarning: coroutine '_execute_swap' was never awaited. Changing the mocked
receipt status from "complete" to 1 leaves the test passing, because neither value
is ever read.

Reproduction. A test that actually executes the coroutine:

"""EmberTail A1: the Permit2 approval path always reports failure.

cdp_smart_wallet_action_provider.swap() sends an approval transaction, then checks

    receipt = wallet_provider.wait_for_transaction_receipt(approval_tx_hash)
    if receipt.status != "complete":
        return {"success": False, "error": "Approval transaction failed"}

CdpSmartWalletProvider.wait_for_transaction_receipt returns
self._web3.eth.wait_for_transaction_receipt(...), a web3.py receipt whose `status` is the
INTEGER 1 on success. `1 != "complete"` is always true, so a successful approval is
reported as a failure.

AgentKit's own test for this path mocks asyncio's run_until_complete to return a canned
success dict, so the coroutine containing this check never executes. This test runs it.

    pytest tests/embertail_a1_approval_status.py -q
"""

from contextlib import asynccontextmanager
from unittest.mock import AsyncMock, Mock, patch

import pytest

from coinbase_agentkit.action_providers.cdp.cdp_smart_wallet_action_provider import (
    CdpSmartWalletActionProvider,
)
from coinbase_agentkit.network import Network

FROM_TOKEN = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
TO_TOKEN = "0x4200000000000000000000000000000000000006"


def _provider_with(receipt_status):
    """A smart-wallet provider whose approval receipt carries `receipt_status`."""
    wallet = Mock()
    wallet.get_network.return_value = Network(protocol_family="evm", network_id="base-mainnet")
    wallet.send_transaction.return_value = "0xapproval"
    wallet.wait_for_transaction_receipt.return_value = Mock(status=receipt_status)
    wallet._paymaster_url = None

    # A quote that reports an allowance issue, so the approval branch is taken.
    quote = Mock()
    quote.liquidity_available = True
    issues = Mock(spec=["allowance"])  # spec so hasattr(issues, "balance") is False
    issues.allowance = Mock(required_allowance="1", current_allowance="0")
    quote.issues = issues
    quote.execute = AsyncMock(return_value=Mock(user_op_hash="0xuserop"))

    smart_account = Mock()
    smart_account.quote_swap = AsyncMock(return_value=quote)
    # The user-operation receipt genuinely is a string status; only the approval
    # receipt above comes from web3.py.
    smart_account.wait_for_user_operation = AsyncMock(return_value=Mock(status="complete"))
    wallet._get_smart_account = AsyncMock(return_value=smart_account)

    @asynccontextmanager
    async def _client(*_a, **_k):
        yield Mock()

    wallet.get_client.return_value = _client()
    return wallet


def _run_swap(wallet):
    provider = CdpSmartWalletActionProvider()
    with (
        patch.object(provider, "_get_client", return_value=wallet.get_client.return_value),
        patch(
            "coinbase_agentkit.action_providers.cdp.cdp_smart_wallet_action_provider.get_token_details",
            return_value={
                "from_token_decimals": 6,
                "to_token_decimals": 18,
                "from_token_name": "USDC",
                "to_token_name": "WETH",
            },
        ),
        patch(
            "coinbase_agentkit.action_providers.cdp.cdp_smart_wallet_action_provider.Web3"
        ) as web3,
    ):
        web3.return_value.eth.contract.return_value.encodeABI.return_value = "0xdata"
        return provider.swap(
            wallet,
            {
                "from_token": FROM_TOKEN,
                "to_token": TO_TOKEN,
                "from_amount": "1",
                "slippage_bps": 100,
            },
        )


def test_integer_receipt_status_reports_a_successful_approval_as_failed():
    """What the real provider returns: web3.py's integer status 1."""
    result = _run_swap(_provider_with(1))
    print(f"\n  receipt.status = 1 (what web3.py returns) -> {result}")
    assert "Approval transaction failed" in str(result), (
        "expected the always-false comparison to report failure"
    )


def test_string_receipt_status_succeeds():
    """The control: the status the code is written for. Shows the test harness is sound."""
    result = _run_swap(_provider_with("complete"))
    print(f"\n  receipt.status = 'complete' (what the code expects) -> {result}")
    assert "Approval transaction failed" not in str(result)

Results: with status = 1, the result is
{"success": false, "error": "Approval transaction failed"}. With status = "complete"
it passes the approval branch.

Suggested fix. Accept both shapes:

status = getattr(receipt, "status", None)
if status not in (1, "complete", "success"):
    return {"success": False, "error": "Approval transaction failed"}

and give the existing test a real event loop so the branch is covered.

Note that line 322 in the same function compares a user operation receipt to
"complete", which is correct — only the web3.py receipt at the approval step is
affected.

Found by EmberTail (https://embertail.xyz). The analysis was assisted by AI tools; the result above comes from running the code. We would be glad to open a pull request with the fix and a covering test.

Abhinav
EmberTail

Lingua principale
TypeScript
Stelle
1.3k
Fork
871
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di coinbase/agentkit

Tutte le issue di coinbase/agentkit

Issue simili

Altre issue su TypeScript

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.